Search code examples
pythonffmpegmp4color-spacepyav

Slightly wrong color in MP4 videos written by PyAV


I am writing MP4 video files with the following PyAV-based code (getting input frames represented as numpy arrays - the sort produced by imageio.imread - as input):

class MP4:
    def __init__(self, fname, width, height, fps):
        self.output = av.open(fname, 'w', format='mp4')
        self.stream = self.output.add_stream('h264', str(fps))
        self.stream.width = width
        self.stream.height = height
        # these 2 lines can be removed and the problem still reproduces:
        self.stream.pix_fmt = 'yuv420p'
        self.stream.options = {'crf': '17'}
    def write_frame(self, pixels):
        frame = av.VideoFrame.from_ndarray(pixels, format='rgb24')
        packet = self.stream.encode(frame)
        self.output.mux(packet)
    def close(self):
        packet = self.stream.encode(None)
        self.output.mux(packet)
        self.output.close()

The colors in the output MP4 video are slightly different (apparently darker) than the colors in the input images:

Screen grab of an image viewer showing an input frame:

source image

Screen grab of VLC playing the output MP4 video:

MP4 output

How can this problem be fixed? I variously fiddled with the frame.colorspace attribute, stream options and VideoFrame.reformat but it changed nothing; of course I could have been fiddling incorrectly.

As you can see, the input has simple flat color regions, so I doubt it's any sort of compression artifact, eg YUV420 dropping some of the chroma info or other such.


Solution

  • Adding frame = frame.reformat(format='yuv420p', dst_colorspace=av.video.reformatter.Colorspace.ITU709) before the call to encode fixes the problem. I don't know why both the format and the dst_colorspace arguments are needed for this to work, empirically they are.