jellyfin / jellyfin-ffmpeg

FFmpeg for Jellyfin
https://jellyfin.org
Other
500 stars 131 forks source link

Transcoding results in significant hue shift #236

Closed ndom91 closed 1 year ago

ndom91 commented 1 year ago

Describe The Bug

When transcoding certain 4K files, I experience a significant hue shift in the resulting content. See screenshot below for a sample. This happens whether or not its transcoding to insert subtitles, or just because of bandwidth / screen size.

Expected Behavior

System (please complete the following information):

MediaInfo

File Video Details ```robots Title: 4K HEVC HDR Codec: HEVC Codec tag: HDMV Profile: Main 10 Level: 150 Resolution: 3840x2160 Aspect ratio: 16:9 Interlaced: No Framerate: 23.976025 Bitrate: 20167 kbps Bit depth: 10 bit Video range: HDR Video range type: DOVI DV title: DV Profile 5 DV version major: 1 DV version minor: 0 DV profile: 5 DV level: 6 DV rpu preset flag: 1 DV el preset flag: 0 DV bl preset flag: 1 DV bl signal compatibility id: 0 Pixel format: yuv420p10le Ref frames: 1 ```
File Audio Details ```robots Title: English - Dolby Digital+ - 5.1 Language: eng Codec: EAC3 Codec tag: EAC3 Layout: 5.1 Channels: 6 ch Bitrate: 768 kbps Sample rate: 48000 Hz Default: No Forced: No External: No ```
File Subtitle Details ```robots Title: English - PGSSUB Language: eng Codec: PGSSUB Default: No Forced: No External: No ```

FFmpeg Logs

`ffmpeg` command from logs ```sh "/usr/lib/jellyfin-ffmpeg/ffmpeg" "-analyzeduration 50M -f mpegts -autorotate 0 -i file:\"/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4\" -map 0:0 -map 0:1 -map -0:s -codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" -vf \"setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709,scale=trunc(min(max(iw\,ih*a)\,min(3840\,2160*a))/2)*2:trunc(min(max(iw/a\,ih)\,min(3840/a\,2160))/2)*2,format=yuv420p\" -preset superfast -crf 23 -maxrate 33612415 -bufsize 67224830 -profile:v:0 high -level 51 -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none -map_metadata -1 -map_chapters -1 -threads 0 -codec:a:0 libfdk_aac -ac 2 -ab 384000 -af \"volume=2\" -f mp4 -movflags frag_keyframe+empty_moov -y \"/var/lib/jellyfin/transcodes/fbb8a97c1b2833fb4254ed668ce0ba61.mp4\"" ```

Additional Context

Example 4K Last of Us Transcode:

image

Transcode Details ```robots Playback Info Player:Html Video Player Play method:Transcoding Protocol:https Stream type:Video Video Info Player dimensions:1856x1226 Video resolution:3840x2160 Dropped frames:17 Corrupted frames:0 Transcoding Info Video codec:H264 Audio codec:AAC Audio channels:2 Bitrate:19.3 Mbps Transcoding progress:7.9% Transcoding framerate:27 fps Reason for transcoding: The container is not supported The audio codec is not supported The video's range type is not supported Original Media Info Container:mpegts Size:7.1 GiB Bitrate:21.3 Mbps Video codec:HEVC Main 10 Video bitrate:20.2 Mbps Video range type:DOVI Audio codec:EAC3 Audio bitrate:768 kbps Audio channels:6 Audio sample rate:48000 Hz ```
Logs for context ```sh [2023-04-03 13:24:53.385 +00:00] [DBG] GetPostedPlaybackInfo profile: DeviceProfile { Name: null, Id: null, Identification: null, FriendlyName: null, Manufacturer: null, ManufacturerUrl: null, ModelName: null, ModelDescription: null, ModelNumber: null, ModelUrl: null, SerialNumber: null, EnableAlbumArtInDidl: False, EnableSingleAlbumArtLimit: False, EnableSingleSubtitleLimit: False, SupportedMediaTypes: "Audio,Photo,Video", UserId: null, AlbumArtPn: null, MaxAlbumArtWidth: null, MaxAlbumArtHeight: null, MaxIconWidth: null, MaxIconHeight: null, MaxStreamingBitrate: 120000000, MaxStaticBitrate: 100000000, MusicStreamingTranscodingBitrate: 384000, MaxStaticMusicBitrate: 8000000, SonyAggregationFlags: null, ProtocolInfo: null, TimelineOffsetSeconds: 0, RequiresPlainVideoItems: False, RequiresPlainFolders: False, EnableMSMediaReceiverRegistrar: False, IgnoreTranscodeByteRangeRequests: False, XmlRootAttributes: [], DirectPlayProfiles: [DirectPlayProfile { Container: "webm", AudioCodec: "vorbis,opus", VideoCodec: "vp8,vp9,av1", Type: Video }, DirectPlayProfile { Container: "mp4,m4v", AudioCodec: "aac,mp3,opus,flac,vorbis", VideoCodec: "h264,hevc,vp9,av1", Type: Video }, DirectPlayProfile { Container: "mov", AudioCodec: "aac,mp3,opus,flac,vorbis", VideoCodec: "h264", Type: Video }, DirectPlayProfile { Container: "opus", AudioCodec: null, VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "webm", AudioCodec: "opus", VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "mp3", AudioCodec: null, VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "aac", AudioCodec: null, VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "m4a", AudioCodec: "aac", VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "m4b", AudioCodec: "aac", VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "flac", AudioCodec: null, VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "webma", AudioCodec: null, VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "webm", AudioCodec: "webma", VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "wav", AudioCodec: null, VideoCodec: null, Type: Audio }, DirectPlayProfile { Container: "ogg", AudioCodec: null, VideoCodec: null, Type: Audio }], TranscodingProfiles: [TranscodingProfile { Container: "ts", Type: Audio, VideoCodec: "", AudioCodec: "aac", Protocol: "hls", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 1, SegmentLength: 0, BreakOnNonKeyFrames: True, Conditions: [] }, TranscodingProfile { Container: "aac", Type: Audio, VideoCodec: "", AudioCodec: "aac", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "mp3", Type: Audio, VideoCodec: "", AudioCodec: "mp3", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "opus", Type: Audio, VideoCodec: "", AudioCodec: "opus", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "wav", Type: Audio, VideoCodec: "", AudioCodec: "wav", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "opus", Type: Audio, VideoCodec: "", AudioCodec: "opus", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Static, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "mp3", Type: Audio, VideoCodec: "", AudioCodec: "mp3", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Static, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "aac", Type: Audio, VideoCodec: "", AudioCodec: "aac", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Static, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "wav", Type: Audio, VideoCodec: "", AudioCodec: "wav", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Static, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "ts", Type: Video, VideoCodec: "h264", AudioCodec: "aac,mp3", Protocol: "hls", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 1, SegmentLength: 0, BreakOnNonKeyFrames: True, Conditions: [] }, TranscodingProfile { Container: "mp4", Type: Video, VideoCodec: "h264,hevc,vp9,av1", AudioCodec: "aac,mp3,opus,flac,vorbis", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Streaming, EnableSubtitlesInManifest: False, MaxAudioChannels: "2", MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }, TranscodingProfile { Container: "mp4", Type: Video, VideoCodec: "h264", AudioCodec: "aac,mp3,opus,flac,vorbis", Protocol: "http", EstimateContentLength: False, EnableMpegtsM2TsMode: False, TranscodeSeekInfo: Auto, CopyTimestamps: False, Context: Static, EnableSubtitlesInManifest: False, MaxAudioChannels: null, MinSegments: 0, SegmentLength: 0, BreakOnNonKeyFrames: False, Conditions: [] }], ContainerProfiles: [], CodecProfiles: [CodecProfile { Type: VideoAudio, Conditions: [ProfileCondition { Condition: Equals, Property: IsSecondaryAudio, Value: "false", IsRequired: False }], ApplyConditions: [], Codec: "aac", Container: null }, CodecProfile { Type: VideoAudio, Conditions: [ProfileCondition { Condition: Equals, Property: IsSecondaryAudio, Value: "false", IsRequired: False }], ApplyConditions: [], Codec: null, Container: null }, CodecProfile { Type: Video, Conditions: [ProfileCondition { Condition: NotEquals, Property: IsAnamorphic, Value: "true", IsRequired: False }, ProfileCondition { Condition: EqualsAny, Property: VideoProfile, Value: "high|main|baseline|constrained baseline|high 10", IsRequired: False }, ProfileCondition { Condition: EqualsAny, Property: VideoRangeType, Value: "SDR", IsRequired: False }, ProfileCondition { Condition: LessThanEqual, Property: VideoLevel, Value: "52", IsRequired: False }, ProfileCondition { Condition: NotEquals, Property: IsInterlaced, Value: "true", IsRequired: False }], ApplyConditions: [], Codec: "h264", Container: null }, CodecProfile { Type: Video, Conditions: [ProfileCondition { Condition: NotEquals, Property: IsAnamorphic, Value: "true", IsRequired: False }, ProfileCondition { Condition: EqualsAny, Property: VideoProfile, Value: "main|main 10", IsRequired: False }, ProfileCondition { Condition: EqualsAny, Property: VideoRangeType, Value: "SDR", IsRequired: False }, ProfileCondition { Condition: LessThanEqual, Property: VideoLevel, Value: "183", IsRequired: False }, ProfileCondition { Condition: NotEquals, Property: IsInterlaced, Value: "true", IsRequired: False }], ApplyConditions: [], Codec: "hevc", Container: null }, CodecProfile { Type: Video, Conditions: [ProfileCondition { Condition: EqualsAny, Property: VideoRangeType, Value: "SDR|HDR10|HLG", IsRequired: False }], ApplyConditions: [], Codec: "vp9", Container: null }, CodecProfile { Type: Video, Conditions: [ProfileCondition { Condition: EqualsAny, Property: VideoRangeType, Value: "SDR|HDR10|HLG", IsRequired: False }], ApplyConditions: [], Codec: "av1", Container: null }], ResponseProfiles: [ResponseProfile { Container: "m4v", AudioCodec: null, VideoCodec: null, Type: Video, OrgPn: null, MimeType: "video/mp4", Conditions: [] }], SubtitleProfiles: [SubtitleProfile { Format: "vtt", Method: External, DidlMode: null, Language: null, Container: null }, SubtitleProfile { Format: "ass", Method: External, DidlMode: null, Language: null, Container: null }, SubtitleProfile { Format: "ssa", Method: External, DidlMode: null, Language: null, Container: null }] } [2023-04-03 13:24:53.386 +00:00] [INF] User policy for "ndo". EnablePlaybackRemuxing: True EnableVideoPlaybackTranscoding: True EnableAudioPlaybackTranscoding: True [2023-04-03 13:24:53.386 +00:00] [DBG] Profile: "Unknown Profile", Path: "/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4", isEligibleForDirectPlay: True, isEligibleForDirectStream: False [2023-04-03 13:24:53.386 +00:00] [DBG] Profile: "VideoCodecProfile", DirectPlay=false. Reason="Unknown Profile".VideoRangeType Condition: EqualsAny. ConditionValue: "SDR". IsRequired: False. Path: "/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4" [2023-04-03 13:24:53.386 +00:00] [DBG] DirectPlay Result for Profile: "Anonymous Profile", Path: "/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4", PlayMethod: null, AudioStreamIndex: 1, SubtitleStreamIndex: null, Reasons: ContainerNotSupported, AudioCodecNotSupported, VideoRangeTypeNotSupported [2023-04-03 13:24:53.386 +00:00] [DBG] Transcode Result for Profile: "Anonymous Profile", Path: "/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4", PlayMethod: Transcode, AudioStreamIndex: 1, SubtitleStreamIndex: null, Reasons: ContainerNotSupported, AudioCodecNotSupported, VideoRangeTypeNotSupported [2023-04-03 13:24:53.386 +00:00] [INF] StreamBuilder.BuildVideoItem( Profile="Anonymous Profile", Path="/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4", AudioStreamIndex=null, SubtitleStreamIndex=null ) => ( PlayMethod=Transcode, TranscodeReason=ContainerNotSupported, AudioCodecNotSupported, VideoRangeTypeNotSupported ) "media:/videos/a896a1c1-3506-45b3-abdf-d3527b83f08c/stream.mp4?MediaSourceId=a896a1c1350645b3abdfd3527b83f08c&VideoCodec=h264,hevc,vp9,av1,hevc&AudioCodec=aac,mp3,opus,flac,vorbis&AudioStreamIndex=1&VideoBitrate=139616000&AudioBitrate=384000&MaxFramerate=23.976025&api_key=&TranscodingMaxAudioChannels=2&RequireAvc=false&Tag=9d8bbba16b1bf64ff951885c45f19fcc&hevc-level=150&hevc-videobitdepth=10&hevc-profile=main10&h264-profile=high,main,baseline,constrainedbaseline,high10&h264-rangetype=SDR&h264-level=52&h264-deinterlace=true&hevc-rangetype=SDR&hevc-deinterlace=true&vp9-rangetype=SDR,HDR10,HLG&av1-rangetype=SDR,HDR10,HLG&TranscodeReasons=ContainerNotSupported,%20AudioCodecNotSupported,%20VideoRangeTypeNotSupported" [2023-04-03 13:24:53.681 +00:00] [DBG] AuthenticationScheme: "CustomAuthentication" was successfully authenticated. [2023-04-03 13:24:53.685 +00:00] [INF] "/usr/lib/jellyfin-ffmpeg/ffmpeg" "-analyzeduration 50M -f mpegts -autorotate 0 -i file:\"/mnt/media/tv/The Last of Us/Season 1/The Last of Us - S01E08 - When We Are in Need WEBDL-2160p.mp4\" -map 0:0 -map 0:1 -map -0:s -codec:v:0 libx264 -force_key_frames \"expr:gte(t,n_forced*5)\" -vf \"setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709,scale=trunc(min(max(iw\,ih*a)\,min(3840\,2160*a))/2)*2:trunc(min(max(iw/a\,ih)\,min(3840/a\,2160))/2)*2,format=yuv420p\" -preset superfast -crf 23 -maxrate 33612415 -bufsize 67224830 -profile:v:0 high -level 51 -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none -map_metadata -1 -map_chapters -1 -threads 0 -codec:a:0 libfdk_aac -ac 2 -ab 384000 -af \"volume=2\" -f mp4 -movflags frag_keyframe+empty_moov -y \"/var/lib/jellyfin/transcodes/fbb8a97c1b2833fb4254ed668ce0ba61.mp4\"" [2023-04-03 13:24:53.687 +00:00] [DBG] Launched FFmpeg process [2023-04-03 13:24:53.695 +00:00] [DBG] Waiting for the creation of "/var/lib/jellyfin/transcodes/fbb8a97c1b2833fb4254ed668ce0ba61.mp4" [2023-04-03 13:24:53.796 +00:00] [DBG] File "/var/lib/jellyfin/transcodes/fbb8a97c1b2833fb4254ed668ce0ba61.mp4" created or transcoding has finished [2023-04-03 13:24:54.798 +00:00] [DBG] StartFfMpeg() finished successfully [2023-04-03 13:24:54.798 +00:00] [WRN] Slow HTTP Response from "http://jellyfin.domain.com/videos/a896a1c1-3506-45b3-abdf-d3527b83f08c/stream.mp4?DeviceId=123ABC&MediaSourceId=a896a1c1350645b3abdfd3527b83f08c&VideoCodec=h264,hevc,vp9,av1,hevc&AudioCodec=aac,mp3,opus,flac,vorbis&AudioStreamIndex=1&VideoBitrate=139616000&AudioBitrate=384000&MaxFramerate=23.976025&PlaySessionId=a6584f59f3b6480785f266b97036f8ad&api_key=abc123&TranscodingMaxAudioChannels=2&RequireAvc=false&Tag=9d8bbba16b1bf64ff951885c45f19fcc&hevc-level=150&hevc-videobitdepth=10&hevc-profile=main10&h264-profile=high,main,baseline,constrainedbaseline,high10&h264-rangetype=SDR&h264-level=52&h264-deinterlace=true&hevc-rangetype=SDR&hevc-deinterlace=true&vp9-rangetype=SDR,HDR10,HLG&av1-rangetype=SDR,HDR10,HLG&TranscodeReasons=ContainerNotSupported,%20AudioCodecNotSupported,%20VideoRangeTypeNotSupported" to "10.0.1.1" in 0:00:01.1181407 with Status Code 200 [2023-04-03 13:24:56.335 +00:00] [DBG] AuthenticationScheme: "CustomAuthentication" was successfully authenticated. [2023-04-03 13:24:56.351 +00:00] [DBG] AuthenticationScheme: "CustomAuthentication" was successfully authenticated. [2023-04-03 13:24:56.351 +00:00] [DBG] AuthenticationScheme: "CustomAuthentication" was successfully authenticated. ```
nyanmisaka commented 1 year ago

@ndom91 This is the expected behavior when playing Dolby Vision Profile 5 video on a SDR display without doing correct tone-mapping. You should see this post and our docs to setup OpenCL tone-mapping with your Intel iGPU.

ndom91 commented 1 year ago

Great, thanks for the quick response.

I don't have an intel iGPU, however, and this effect is also happening when transcoding into 4K on a 4K TV (i.e. when subtitles have been enabled).

ndom91 commented 1 year ago

Also this file, for example, does seem to be encoded in YUV, not ITP(ICtCp).

From "Video file details": Pixel format: yuv420p10le - right?

nyanmisaka commented 1 year ago

Intel(R) Xeon(R) E-2356G CPU

Processor Graphics ‡ Intel® UHD Graphics P750

As per the intel ark your CPU has a UHD P750 iGPU.

FFmpeg doesn't recognize ITP, so it's still listed as YUV.

ndom91 commented 1 year ago

Intel(R) Xeon(R) E-2356G CPU

Processor Graphics ‡ Intel® UHD Graphics P750

As per the intel ark your CPU has a UHD P750 iGPU.

FFmpeg doesn't recognize ITP, so it's still listed as YUV.

Yeah I should have mentioned more detail, I know it has an iGPU, but the supermicro motherboard it's on doesn't support iGPUs and its not visible under any OS.

I didn't realize the Supermicro issue until after id purchased the server and dug around every bios setting, etc. that I could find 😂. What vendor would sell this configuration?! Haha

ndom91 commented 1 year ago

Its got an Intel C252 chipset - https://www.intel.com/content/www/us/en/products/sku/213680/intel-c252-chipset/specifications.html

nyanmisaka commented 1 year ago

That's a real shame. As far as I know, some motherboards do not design power phases for integrated graphics.

But currently Jellyfin only supports transcoding DoVi P5 video using GPU, so in theory QSV will be perfect for this task once you replace the motherboard.

ndom91 commented 1 year ago

Okay thanks for confirming, I'll close the issue for now then 🙏