/* JellyfinPlayer/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 2021 Aiden Vigue & Jellyfin Contributors */ //lol can someone buy me a coffee this took forever :| import Foundation import JellyfinAPI enum CPUModel { case A4 case A5 case A5X case A6 case A6X case A7 case A7X case A8 case A8X case A9 case A9X case A10 case A10X case A11 case A12 case A12X case A12Z case A13 case A14 case A99 } class DeviceProfileBuilder { public var bitrate: Int = 0; public func setMaxBitrate(bitrate: Int) { self.bitrate = bitrate } public func buildProfile() -> DeviceProfile { let maxStreamingBitrate = bitrate; let maxStaticBitrate = bitrate; let musicStreamingTranscodingBitrate = 384000; //Build direct play profiles var directPlayProfiles: [DirectPlayProfile] = []; directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav", videoCodec: "h264", type: .video)] //Device supports Dolby Digital (AC3, EAC3) if(supportsFeature(minimumSupported: .A8X)) { if(supportsFeature(minimumSupported: .A10)) { directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1", type: .video)] //HEVC/H.264 with Dolby Digital } else { directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264", type: .video)] //H.264 with Dolby Digital } } //Device supports Dolby Vision? if(supportsFeature(minimumSupported: .A10X)) { directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] //H.264/HEVC with Dolby Digital - No Atmos - Vision } //Device supports Dolby Atmos? if(supportsFeature(minimumSupported: .A12)) { directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] //H.264/HEVC with Dolby Digital & Atmos - Vision } //Build transcoding profiles var transcodingProfiles: [TranscodingProfile] = []; transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav")] //Device supports Dolby Digital (AC3, EAC3) if(supportsFeature(minimumSupported: .A8X)) { if(supportsFeature(minimumSupported: .A10)) { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "aac,mp3,wav,eac3,ac3,flac,opus", audioCodec: "h264,hevc,hev1", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } else { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } } //Device supports Dolby Vision? if(supportsFeature(minimumSupported: .A10X)) { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dva1,dvav,dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } //Device supports Dolby Atmos? if(supportsFeature(minimumSupported: .A12)) { transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dva1,dvav,dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,dts,truehd,dca,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)] } var codecProfiles: [CodecProfile] = [] let h264CodecConditions: [ProfileCondition] = [ ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", isRequired: false), ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "60", isRequired: false), ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)] let hevcCodecConditions: [ProfileCondition] = [ ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false), ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "main|main 10", isRequired: false), ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "160", isRequired: false), ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)] codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264")) if(supportsFeature(minimumSupported: .A10)) { codecProfiles.append(CodecProfile(type: .video, applyConditions: hevcCodecConditions,codec: "hevc")) } var subtitleProfiles: [SubtitleProfile] = [] subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "ass", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external)) subtitleProfiles.append(SubtitleProfile(format: "pgssub", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "sub", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "rip", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "srt", method: .embed)) subtitleProfiles.append(SubtitleProfile(format: "pgs", method: .embed)) let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", type: .video, mimeType: "video/mp4")] let profile = DeviceProfile(maxStreamingBitrate: maxStreamingBitrate, maxStaticBitrate: maxStaticBitrate, musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate, directPlayProfiles: directPlayProfiles, transcodingProfiles: transcodingProfiles, containerProfiles: [], codecProfiles: codecProfiles, responseProfiles: responseProfiles, subtitleProfiles: subtitleProfiles) return profile } private func supportsFeature(minimumSupported: CPUModel) -> Bool { let intValues: [CPUModel: Int] = [.A4: 1, .A5: 2, .A5X: 3, .A6: 4, .A6X: 5, .A7: 6, .A7X: 7, .A8: 8, .A8X: 9, .A9: 10, .A9X: 11, .A10: 12, .A10X: 13, .A11: 14, .A12: 15, .A12X: 16, .A12Z: 16, .A13: 17, .A14: 18, .A99: 99] return intValues[CPUinfo()] ?? 0 >= intValues[minimumSupported] ?? 0 } /********************************************** * CPUInfo(): * Returns a hardcoded value of the current * devices CPU name. ***********************************************/ private func CPUinfo() -> CPUModel { #if targetEnvironment(simulator) let identifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]! #else var systemInfo = utsname() uname(&systemInfo) let machineMirror = Mirror(reflecting: systemInfo.machine) let identifier = machineMirror.children.reduce("") { identifier, element in guard let value = element.value as? Int8 , value != 0 else { return identifier } return identifier + String(UnicodeScalar(UInt8(value))) } #endif switch identifier { case "iPod5,1": return .A5 case "iPod7,1": return .A8 case "iPod9,1": return .A10 case "iPhone3,1", "iPhone3,2", "iPhone3,3": return .A4 case "iPhone4,1": return .A5 case "iPhone5,1", "iPhone5,2": return .A6 case "iPhone5,3", "iPhone5,4": return .A6 case "iPhone6,1", "iPhone6,2": return .A7 case "iPhone7,2": return .A8 case "iPhone7,1": return .A8 case "iPhone8,1": return .A9 case "iPhone8,2", "iPhone8,4": return .A9 case "iPhone9,1", "iPhone9,3": return .A10 case "iPhone9,2", "iPhone9,4": return .A10 case "iPhone10,1", "iPhone10,4": return .A11 case "iPhone10,2", "iPhone10,5": return .A11 case "iPhone10,3", "iPhone10,6": return .A11 case "iPhone11,2", "iPhone11,6", "iPhone11,8": return .A12 case "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8": return .A13 case "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4": return .A14 case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return .A5 case "iPad3,1", "iPad3,2", "iPad3,3": return .A5X case "iPad3,4", "iPad3,5", "iPad3,6": return .A6X case "iPad4,1", "iPad4,2", "iPad4,3": return .A7 case "iPad5,3", "iPad5,4": return .A8X case "iPad6,11", "iPad6,12": return .A9 case "iPad2,5", "iPad2,6", "iPad2,7": return .A5 case "iPad4,4", "iPad4,5", "iPad4,6": return .A7 case "iPad4,7", "iPad4,8", "iPad4,9": return .A7 case "iPad5,1", "iPad5,2": return .A8 case "iPad11,1", "iPad11,2": return .A12 case "iPad6,3", "iPad6,4": return .A9X case "iPad6,7", "iPad6,8": return .A9X case "iPad7,1", "iPad7,2": return .A10X case "iPad7,3", "iPad7,4": return .A10X case "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12": return .A10 case "iPad8,1", "iPad8,2" ,"iPad8,3", "iPad8,4": return .A12X case "iPad8,5", "iPad8,6" ,"iPad8,7", "iPad8,8": return .A12X case "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12": return .A12Z case "iPad11,3", "iPad11,4" ,"iPad11,6", "iPad11,7": return .A12 case "iPad13,1", "iPad13,2": return .A14 case "AppleTV5,3": return .A8 case "AppleTV6,2": return .A10X case "AudioAccessory1,1": return .A8 default: return .A99 } } }