H264 from AVC Intra Colours Wrong
H264 from AVC Intra Colours Wrong
Hi,
I've noticed today that the H264's coming out of my workflows from 10bit Rec 709 AVC Intra 100 MXF files are lifted.
Adobe Media Encoder Exports are spot on using the same specifications but FFastrans is lifting the colours.
I've attached the settings used for FFastrans and the colour settings in the Adobe export as well as the source AVC Intra 100 settings.
Also two visuals references of Adobe and FFastrans export.
Any ideas? I've played around with the settings in FFastrans and nothing I've tried fixes this.
Update: I've reverted all the way back to 1.3.0 which I've been using for a long time and the issue goes away. I'm not sure if there's a way I can fix this in 1.4.0.7?
I've noticed today that the H264's coming out of my workflows from 10bit Rec 709 AVC Intra 100 MXF files are lifted.
Adobe Media Encoder Exports are spot on using the same specifications but FFastrans is lifting the colours.
I've attached the settings used for FFastrans and the colour settings in the Adobe export as well as the source AVC Intra 100 settings.
Also two visuals references of Adobe and FFastrans export.
Any ideas? I've played around with the settings in FFastrans and nothing I've tried fixes this.
Update: I've reverted all the way back to 1.3.0 which I've been using for a long time and the issue goes away. I'm not sure if there's a way I can fix this in 1.4.0.7?
- Attachments
-
- Source AVC MXF Settings
- Source AVC Intra Settings.png (25.61 KiB) Viewed 5496 times
-
- Adobe ME MP4 Output Settings
- Adobe Export Settings.png (23.97 KiB) Viewed 5500 times
-
- FFASTRANS MP4 Output Settings
- FFastrans Settings.png (179.93 KiB) Viewed 5500 times
Last edited by graham728 on Fri Jan 31, 2025 5:26 pm, edited 3 times in total.
Re: H264 from AVC Intra Colours Wrong
Bypassing the AV Decoder sorts the issue - but I need that for AV Filters...
I would add I have MXF DNxHD nodes attached to the AV Decoder and they come out fine. It's only the H264s that are coming out lifted post going through the AV Decoder.
I would add I have MXF DNxHD nodes attached to the AV Decoder and they come out fine. It's only the H264s that are coming out lifted post going through the AV Decoder.
Re: H264 from AVC Intra Colours Wrong
When the answer is not easy we usually want to reproduce the issue to see whats going on. Please give us as much as you can, best workflow+media but if you cant do that, at least job logs and or better a zip of a job_work folder that contains all the avs files and other temp files.
emcodem, wrapping since 2009 you got the rhyme?
Re: H264 from AVC Intra Colours Wrong
Please find attached
MXF Intra exported from AME which needs to be run through FFastrans
H264 which has been outputted from Untitled Workflow - also attached
https://we.tl/t-ANuhaoCLSO
If you shot check both files you'll notice the H264 is lifted. I've tried changing all the settings in AV Decoder and in the H264 but can't get it to not do this. It doesn't affect MXF exports using the same AV Decoder.
As per above, using this exact process in 1.3.0 works just fine.
MXF Intra exported from AME which needs to be run through FFastrans
H264 which has been outputted from Untitled Workflow - also attached
https://we.tl/t-ANuhaoCLSO
If you shot check both files you'll notice the H264 is lifted. I've tried changing all the settings in AV Decoder and in the H264 but can't get it to not do this. It doesn't affect MXF exports using the same AV Decoder.
As per above, using this exact process in 1.3.0 works just fine.
Re: H264 from AVC Intra Colours Wrong
Fantastic, that makes our life a lot easier! ...i can see the gamma shift in the mp4 you uploaded but don't have time "now" to dig into this... needs a few hours
emcodem, wrapping since 2009 you got the rhyme?
Re: H264 from AVC Intra Colours Wrong
That's great, thanks Emcodem
Re: H264 from AVC Intra Colours Wrong
Sorry for the lengthy post, scroll to bottom for solution.
OK easy enough, i was able to find out that the base problem comes from your input mxf. There are 2 different metadata for "video range" in the file, one the mxf header, the other one the video stream headers. MXF says Limited, Video stream says Full. Premiere produced a buggy file actually.
Mediainfo knows both values:
As you already played with the h264 processors range setting, i guess you already had a feeling about it.
Now, as you also already said, it depends on the decoder (e.g. avisynth or ffmpeg) if it assumes the source is Full or Limited range but this is one of the points where many decoders do not follow the rules: the standards say that the metadata from Header should always take precedence over the ones from stream. This again is stupid because we could not pack an SD stream with changing aspect ratio easily in the mxf correctly. We could but the mxf encoder would need to place a new body partition, reflecting the new video metadata. No decoder on earth would support this.
Anyway, avisynth and ffmpeg decoders behave differently in this regards obviously.
What i found out is that
a) the setting in the mp4 encoder "Set to Limited" (or full) does nothing in this case. Not sure what its supposed to do, maybe @admin knows.
b) if it worked in theory, you would need to set "full" because after avisynth, the clip is marked as full, so you'd need to just keep that in the encoder for preventing conversion.
c) there is some strangeness regarding the ffmpeg filter "format" actually, this seems to be the one that performs the range conversion, this filter is not there when you encode to 4:2:2 output but it is there for your mp4 and it seems to be the one that executes the range conversion, again @admin might be interested.
d) due to a very recent change in ffastrans (change setrange to setprop filter), i believe the problem might be accidently fixed in future ffastrans versions, we need to check that internally.
OK, none of the above is helpful for you in any way.
So, after all as a workaround for you, we need a way to "force" the correct color range before we encode the video.
You can use Custom avisynth script between A/V decoder and the encoders with this content:
If i tested correctly, after this, it does not matter if you encode mp4 or any 4:2:2 format, the range will not be converted if the encoder is set to produce limited range (e.g. xdcam).
OK easy enough, i was able to find out that the base problem comes from your input mxf. There are 2 different metadata for "video range" in the file, one the mxf header, the other one the video stream headers. MXF says Limited, Video stream says Full. Premiere produced a buggy file actually.
Mediainfo knows both values:
Code: Select all
Color range : Limited
colour_range_Source : Container
colour_range_Original : Full
colour_range_Original_Source : Stream
Now, as you also already said, it depends on the decoder (e.g. avisynth or ffmpeg) if it assumes the source is Full or Limited range but this is one of the points where many decoders do not follow the rules: the standards say that the metadata from Header should always take precedence over the ones from stream. This again is stupid because we could not pack an SD stream with changing aspect ratio easily in the mxf correctly. We could but the mxf encoder would need to place a new body partition, reflecting the new video metadata. No decoder on earth would support this.
Anyway, avisynth and ffmpeg decoders behave differently in this regards obviously.
What i found out is that
a) the setting in the mp4 encoder "Set to Limited" (or full) does nothing in this case. Not sure what its supposed to do, maybe @admin knows.
b) if it worked in theory, you would need to set "full" because after avisynth, the clip is marked as full, so you'd need to just keep that in the encoder for preventing conversion.
c) there is some strangeness regarding the ffmpeg filter "format" actually, this seems to be the one that performs the range conversion, this filter is not there when you encode to 4:2:2 output but it is there for your mp4 and it seems to be the one that executes the range conversion, again @admin might be interested.
d) due to a very recent change in ffastrans (change setrange to setprop filter), i believe the problem might be accidently fixed in future ffastrans versions, we need to check that internally.
OK, none of the above is helpful for you in any way.
So, after all as a workaround for you, we need a way to "force" the correct color range before we encode the video.
You can use Custom avisynth script between A/V decoder and the encoders with this content:
Code: Select all
last = m_clip
propSet("_ColorRange", 1)
m_clip = last
Return m_clip
emcodem, wrapping since 2009 you got the rhyme?
Re: H264 from AVC Intra Colours Wrong
Thanks for the detailed response.
I'm not sure if the Adobe Media Encoder MXF is buggy - I've checked as far back as 2019 and that's how AME has been exporting their MXF Intra's - see metadata from a 2019 file I have. I'm using their standard AVC-Intra 100 MXF 1080i Export Preset.
FFastrans 1.3.0 processes these MXFs as expected without lifting the colours. It's only when I've updated to 1.4.0 I've noticed this issue.
I'm not sure what has changed with the AV Decoder since then?
I'm a little hesitant to introduce a custom node before the H264 processor, as not all the inputted files will be the same or AVC-Intra.
Thanks
I'm not sure if the Adobe Media Encoder MXF is buggy - I've checked as far back as 2019 and that's how AME has been exporting their MXF Intra's - see metadata from a 2019 file I have. I'm using their standard AVC-Intra 100 MXF 1080i Export Preset.
FFastrans 1.3.0 processes these MXFs as expected without lifting the colours. It's only when I've updated to 1.4.0 I've noticed this issue.
I'm not sure what has changed with the AV Decoder since then?
I'm a little hesitant to introduce a custom node before the H264 processor, as not all the inputted files will be the same or AVC-Intra.
Thanks
- Attachments
-
- 2019 MXF Intra.png (29.85 KiB) Viewed 5414 times
Re: H264 from AVC Intra Colours Wrong
Hi, all!
Concerning exported from editing software files - we have some file sources after Edius export to mp4 with "magic" metdata inside:
This is very confusing for A/V media decoder so output after normalizing and converting to mxf can be unpredictable (slow video, unsync etc.)
So for such "magic" files only "Full decoding" option in A/V media decoder can help.
Concerning exported from editing software files - we have some file sources after Edius export to mp4 with "magic" metdata inside:
Code: Select all
Bit rate mode : Constant
Bit rate : 12.0 Mb/s
Width : 1 920 pixels
Height : 1 080 pixels
Display aspect ratio : 16:9
Frame rate mode : Constant
>>Frame rate : 50.000 FPS
>>Original frame rate : 25.000 FPS
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
>>Scan type : Interlaced
Scan type, store method : Separated fields
Scan order : Top Field First
So for such "magic" files only "Full decoding" option in A/V media decoder can help.
Re: H264 from AVC Intra Colours Wrong
Hey Graham, can you try with 1.4.2.10? The build is here: https://github.com/steipal/FFAStrans-Public
You just have to click on "Code" and select "Download as zip".
The reason why I'm suggesting this is that I cannot reproduce this with 1.4.2.10 and, frankly speaking, that's exactly what I would expect: the levels to not be changed in the output file, regardless of the "bad" metadata in the input.
Let's try to reason this together.
So first things first, the H.264 encoder has the following options in terms of levels:
- Convert to Limited
- Convert to Full
- Set as Limited
- Set as Full
- Same as source
The difference between the "Convert" and the "Set" is that the first two will attempt to read the info from the file and perform the conversion based on that. For instance, the first checks if the file is Limited and leaves it as is, otherwise if it's flagged as full it will convert from 0-255 to 16-235 (it can scale with bit depth as well, like if you're working at 10bit it will bring it to 64-940 etc). The second one does the opposite, so it expands the range, which means that if your source is limited tv range 16-235 it will expand it to 0-255. As for the "Set as Limited" and "Set as Full" they just set the metadata of the output clip, namely they flag it as either limited tv range or full pc range without actually changing the levels nor caring about what the input was. In other words, that's the same as using --range tv or --range pc in x264 (i.e it just sets the metadata).
Here's the interesting part: you have the H.264 encoder with the following option "Set as Limited" which means that the filter_builder will do absolutely nothing to the input levels before passing the uncompressed A/V stream living in RAM to the x264 encoder. The only thing that setting does is to set the metadata in the output file as Limited TV Range.
I downloaded both samples (the source and the encoded one), indexed them and then I used VideoTek() to see the waveform:
Top is the original AVC Intra Class 100 file with the normal limited tv range levels and the bottom is the output H.264 file with the wrongly compressed levels.
Emcodem already found the issue with the source, which is that it has contradicting information between the stream and the container where one says that it's limited tv range and the other says that it's full pc range, in fact Adobe Premiere exported a raw_video.h264 with the metadata saying "Full PC Range" and then it muxed it in an .mxf container saying "Limited TV Range". But... here's the thing: as long as you have the H.264 encoding node on "Set to Limited" the encoder won't touch the levels, but rather it will just leave them alone and just set the metadata, which is what is happening here.
If you look at the waveform from VideoTek() above on the encoded file, you can actually see that not only it's been compressed twice despite being limited tv range already, but I'm like 90% sure that it has been converted inside Avisynth itself. The reason why I'm saying this is that there's only one function that I know for a fact introduces those "lines" (the horizontal stripes you can see in the waveform) and that function is Levels(). If this happened on the filter_builder level in FFMpeg, we wouldn't have had those "lines" (i.e horizontal stripes) but rather a different waveform. This is corroborated by the fact that by having the encoder set with "Set to Limited" it won't mess with the levels.
Here's the AVS Script created by the A/V Decoder:
There's absolutely nothing wrong here.
Even ConvertToYUV422() won't screw it up 'cause it thinks that it's full pc range so it will just use matrix="PC.709" instead of matrix="Rec.709" under the hood, thus leaving the levels alone.
Frame properties after the Avisynth Script: As you can see, it's set to Full PC Range (i.e _ColorRange == 0) instead of _ColorRange == 1, but it's "fine" 'cause the levels are untouched:
Here's the comparison of the input source alongside the output produced using your simple workflow: Here's the outputted sample: https://we.tl/t-NKR9b6HTMl
Please note that all I said above is true as long as you have A/V Decoder -> H.264 Encoder, however it might not be true if you add additional nodes using Avisynth like A/V Decoder -> Filter X -> H.264 Encoder as the internal logic might be performing different adjustments according to the metadata, but yeah, it should work.
Anyway, please take 1.4.2.10 for a spin and give it a try.
You just have to click on "Code" and select "Download as zip".
The reason why I'm suggesting this is that I cannot reproduce this with 1.4.2.10 and, frankly speaking, that's exactly what I would expect: the levels to not be changed in the output file, regardless of the "bad" metadata in the input.

Let's try to reason this together.
So first things first, the H.264 encoder has the following options in terms of levels:
- Convert to Limited
- Convert to Full
- Set as Limited
- Set as Full
- Same as source
The difference between the "Convert" and the "Set" is that the first two will attempt to read the info from the file and perform the conversion based on that. For instance, the first checks if the file is Limited and leaves it as is, otherwise if it's flagged as full it will convert from 0-255 to 16-235 (it can scale with bit depth as well, like if you're working at 10bit it will bring it to 64-940 etc). The second one does the opposite, so it expands the range, which means that if your source is limited tv range 16-235 it will expand it to 0-255. As for the "Set as Limited" and "Set as Full" they just set the metadata of the output clip, namely they flag it as either limited tv range or full pc range without actually changing the levels nor caring about what the input was. In other words, that's the same as using --range tv or --range pc in x264 (i.e it just sets the metadata).
Here's the interesting part: you have the H.264 encoder with the following option "Set as Limited" which means that the filter_builder will do absolutely nothing to the input levels before passing the uncompressed A/V stream living in RAM to the x264 encoder. The only thing that setting does is to set the metadata in the output file as Limited TV Range.
I downloaded both samples (the source and the encoded one), indexed them and then I used VideoTek() to see the waveform:
Top is the original AVC Intra Class 100 file with the normal limited tv range levels and the bottom is the output H.264 file with the wrongly compressed levels.
Emcodem already found the issue with the source, which is that it has contradicting information between the stream and the container where one says that it's limited tv range and the other says that it's full pc range, in fact Adobe Premiere exported a raw_video.h264 with the metadata saying "Full PC Range" and then it muxed it in an .mxf container saying "Limited TV Range". But... here's the thing: as long as you have the H.264 encoding node on "Set to Limited" the encoder won't touch the levels, but rather it will just leave them alone and just set the metadata, which is what is happening here.
If you look at the waveform from VideoTek() above on the encoded file, you can actually see that not only it's been compressed twice despite being limited tv range already, but I'm like 90% sure that it has been converted inside Avisynth itself. The reason why I'm saying this is that there's only one function that I know for a fact introduces those "lines" (the horizontal stripes you can see in the waveform) and that function is Levels(). If this happened on the filter_builder level in FFMpeg, we wouldn't have had those "lines" (i.e horizontal stripes) but rather a different waveform. This is corroborated by the fact that by having the encoder set with "Set to Limited" it won't mess with the levels.
Here's the AVS Script created by the A/V Decoder:
Code: Select all
_ffas_video = "Z:\wetransfer_test-mxf-avc-intra-100-mp4_2025-01-31_1441\TEST MXF AVC INTRA 100.mxf"
_ffas_audio = "Z:\wetransfer_test-mxf-avc-intra-100-mp4_2025-01-31_1441\TEST MXF AVC INTRA 100.mxf"
_ffas_width = 1920
_ffas_height = 1080
_ffas_work_fdr = "c:\.ffastrans_work_root\20250130-1251-4763-4bc2-15ce9978a82d\20250201-2116-1494-3051-1633f2e36a87"
Import("Z:\FFAStrans-Public-master\processors\AVS_plugins\avsi\mtmodes.avsi")
Import("Z:\FFAStrans-Public-master\processors\AVS_plugins\avsi\_ffas_helpers.avsi")
LoadPlugin("Z:\FFAStrans-Public-master\processors\AVS_plugins\ffms2\x64\ffms2.dll")
LoadPlugin("Z:\FFAStrans-Public-master\processors\AVS_plugins\bas\x64\BestAudioSource.dll")
LoadPlugin("Z:\FFAStrans-Public-master\processors\AVS_plugins\JPSDR\x64\plugins_JPSDR.dll")
video = FFVideoSource("Z:\wetransfer_test-mxf-avc-intra-100-mp4_2025-01-31_1441\TEST MXF AVC INTRA 100.mxf", 0, cachefile = "c:\.ffastrans_work_root\20250130-1251-4763-4bc2-15ce9978a82d\20250201-2116-1494-3051-1633f2e36a87\1-0-0~250201211620568~6748~20250131-1434-5666-48c2-c35b785d87ac~dec_avmedia~ffindex.dat", fpsnum=25 , fpsden=1, seekmode = 0)
audio_null = BlankClip(length=103, width=1920, height=1080, color=$000000, channels=1, audio_rate=48000, fps=25)
audio_1 = audio_null
audio_2 = audio_null
audio_3 = audio_null
audio_4 = audio_null
audio_5 = audio_null
audio_6 = audio_null
audio_7 = audio_null
audio_8 = audio_null
audio_9 = audio_null
audio_10 = audio_null
audio_11 = audio_null
audio_12 = audio_null
audio_13 = audio_null
audio_14 = audio_null
audio_15 = audio_null
audio_16 = audio_null
audio_17 = audio_null
audio_18 = audio_null
audio_19 = audio_null
audio_20 = audio_null
audio_21 = audio_null
audio_22 = audio_null
audio_23 = audio_null
audio_24 = audio_null
audio_25 = audio_null
audio_26 = audio_null
audio_27 = audio_null
audio_28 = audio_null
audio_29 = audio_null
audio_30 = audio_null
audio_31 = audio_null
audio_32 = audio_null
audio = FFAudioSource(_ffas_video, 1, cachefile = "c:\.ffastrans_work_root\20250130-1251-4763-4bc2-15ce9978a82d\20250201-2116-1494-3051-1633f2e36a87\1-0-0~250201211620568~6748~20250131-1434-5666-48c2-c35b785d87ac~dec_avmedia~ffindex.dat").ResampleAudio(48000).ConvertAudioTo16bit()
audio_1 = GetChannel(audio, 1)
audio = FFAudioSource(_ffas_video, 2, cachefile = "c:\.ffastrans_work_root\20250130-1251-4763-4bc2-15ce9978a82d\20250201-2116-1494-3051-1633f2e36a87\1-0-0~250201211620568~6748~20250131-1434-5666-48c2-c35b785d87ac~dec_avmedia~ffindex.dat").ResampleAudio(48000).ConvertAudioTo16bit()
audio_2 = GetChannel(audio, 1)
audio = FFAudioSource(_ffas_video, 3, cachefile = "c:\.ffastrans_work_root\20250130-1251-4763-4bc2-15ce9978a82d\20250201-2116-1494-3051-1633f2e36a87\1-0-0~250201211620568~6748~20250131-1434-5666-48c2-c35b785d87ac~dec_avmedia~ffindex.dat").ResampleAudio(48000).ConvertAudioTo16bit()
audio_3 = GetChannel(audio, 1)
audio = FFAudioSource(_ffas_video, 4, cachefile = "c:\.ffastrans_work_root\20250130-1251-4763-4bc2-15ce9978a82d\20250201-2116-1494-3051-1633f2e36a87\1-0-0~250201211620568~6748~20250131-1434-5666-48c2-c35b785d87ac~dec_avmedia~ffindex.dat").ResampleAudio(48000).ConvertAudioTo16bit()
audio_4 = GetChannel(audio, 1)
audio = MergeChannels(audio_1, audio_2, audio_3, audio_4, audio_5, audio_6, audio_7, audio_8, audio_9, audio_10, audio_11, audio_12, audio_13, audio_14, audio_15, audio_16, audio_17, audio_18, audio_19, audio_20, audio_21, audio_22, audio_23, audio_24, audio_25, audio_26, audio_27, audio_28, audio_29, audio_30, audio_31, audio_32)
Global m_clip = AudioDub(video, audio)
m_clip = RemoveAlphaPlane(m_clip)
m_clip = SetChannelMask(m_clip, false, 0)
m_clip = ConvertToYUV422(m_clip, interlaced=true)
m_clip = ConvertBits(m_clip, 8, dither=1)
m_clip = AssumeFrameBased(m_clip)
m_clip = AssumeTFF(m_clip)
Return m_clip
There's absolutely nothing wrong here.
Even ConvertToYUV422() won't screw it up 'cause it thinks that it's full pc range so it will just use matrix="PC.709" instead of matrix="Rec.709" under the hood, thus leaving the levels alone.
Frame properties after the Avisynth Script: As you can see, it's set to Full PC Range (i.e _ColorRange == 0) instead of _ColorRange == 1, but it's "fine" 'cause the levels are untouched:
Here's the comparison of the input source alongside the output produced using your simple workflow: Here's the outputted sample: https://we.tl/t-NKR9b6HTMl
Please note that all I said above is true as long as you have A/V Decoder -> H.264 Encoder, however it might not be true if you add additional nodes using Avisynth like A/V Decoder -> Filter X -> H.264 Encoder as the internal logic might be performing different adjustments according to the metadata, but yeah, it should work.
Anyway, please take 1.4.2.10 for a spin and give it a try.
