Search code examples
pythonvideoffmpegmoviepynvenc

nvenc for ffmpeg hardware acceleration with moviepy producing corrupted mp4


I've been trying to create a Python app that takes an mp4 file, an mp3 file, and a dictionary of captions to produce an output mp4 file.

I got it to work well but I found the libx264 codec to be quite slow when calling write_videofile(), especially with the CompositeVideoClip objects. I wanted to use my GPU to accelerate the encoding process, so after doing a lot of research I did the following:

  • install CUDA 12.5 and update drivers for my 1080 Ti
  • install Gyan's ffmpeg build that already has nvenc support (verified this with ffmpeg -codecs)
  • set FFMPEG_BINARY in my Python code to what I have on my PATH

I'm using Windows 10 and write_videofile() with works just fine with the libx264 codec, even after changing the FFMPEG_BINARY. However, when I set codec='h264_nvenc', even though moviepy finishes successfully, the resulting mp4 file seems corrupted. It's totally black and I cannot play it.

Here is my code:

GPU_ACCELERATION = True
if GPU_ACCELERATION:
    os.environ["FFMPEG_BINARY"] = FFMPEG_BINARY

...

# save the file
if GPU_ACCELERATION:
    print("using GPU to accelerate video write")
    video.write_videofile(output_path,
                        codec='h264_nvenc',  # use NVENC for encoding
                        audio_codec='aac',
                        verbose=True)
else:
    print("no GPU acceleration. expect longer times.")
    video.write_videofile(output_path, 
                            codec='libx264',
                            audio_codec='aac',
                            preset='fast',
                            ffmpeg_params=['-crf', '18'])

I spent a long time passing different values in ffmpeg_params but to no avail. I also tried this using change_settings() from moviepy.config to change the FFMPEG_BINARY. I read both the docs for ffmpeg and moviepy but I could not find the issue with my code. I also tested using ffmpeg directly in PowerShell using the following command and it worked fine:

ffmpeg -i input.mp4 -c:v h264_nvenc -preset fast -b:v 1M -c:a aac output.mp4

I also ran ffprobe on my corrupted output mp4 and couldn't see any issues with it from the meta data:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:\path\to\final.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf61.1.100
  Duration: 00:00:59.50, start: 0.000000, bitrate: 4011 kb/s
  Stream #0:0[0x1](und): Video: h264 (High 4:4:4 Predictive) (avc1 / 0x31637661), gbrp(pc, gbr/unknown/unknown, progressive), 606x1080 [SAR 1:1 DAR 101:180], 3870 kb/s, 60 fps, 60 tbr, 15360 tbn (default)
      Metadata:
        handler_name    : VideoHandler
        vendor_id       : [0][0][0][0]
        encoder         : Lavc61.3.100 h264_nvenc
  Stream #0:1[0x2](und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 127 kb/s (default)
      Metadata:
        handler_name    : SoundHandler
        vendor_id       : [0][0][0][0]

I'm considering just doing the video processing directly with ffmpeg using a PowerShell script but if I can somehow get my Python code to work with GPU acceleration, I'd prefer that. Any help is appreciated.


Solution

  • The moviepy output's video stream has High 4:4:4 Predictive profile. This is not widely supported in players. The command line option -pix_fmt yuv420p should be added.