Search code examples
pythonffmpeg

ffmpeg python: Loop single first frame infinitely with audio


I am trying make a video from image and audio with ffmpeg python package, image will loop until the audio is end, I'm tried loop=1 and shortest argument but not working, the ffmpeg maybe run infinitely. This is my code:

audio = ffmpeg.input("test/audios/1.mp3",)
image = ffmpeg.input("test/images/1.jpg", loop="1")
video = ffmpeg.concat(image, audio, v=1, a=1).output("test/test.mp4", shortest=None,  vcodec="mjpeg", acodec="mp3")
ffmpeg.run(video)

information from get_args()

['-loop', '1', '-i', 'test/images/1.jpg', '-i', 'test/audios/1.mp3', '-filter_complex', '[0][1]concat=a=1:n=1:v=1[s0]', '-map', '[s0]', '-acodec', 'mp3', '-shortest', '-vcodec', 'mjpeg', 'test/test.mp4']

Solution

  • The main issue is using ffmpeg.concat for merging the video and the audio.

    ffmpeg.concat applies concat filer.
    It is possible to use concat filter for merging video and audio streams, but the filter is intended for concatenating ("chaining") multiple video (and/or audio) streams.

    The shortest argument doesn't affect the concat filter, so ffmpeg runs infinitely.

    Instead of using concat filter, use the video and audio inputs as first arguments to ffmpeg.output (before "test/test.mp4").


    Other issues:

    • There is a compatibility issue for MJPEG codec and MP4 file container.
      VLC Player plays the MP4 file successfully, but other players may not support it.
      We may create MOV file instead of MP4 (MOV and MP4 are almost the identical).
      (We may also consider selecting different video encoder, like libx264).
      When using MJPEG codec, it is recommended to set pix_fmt='yuvj420p' (for better compatibility).
    • The default frame rate for image loop is 25fps (and that seems too much for a static image).
      We may set the frame rate using framerate=fps.

    Code sample:

    import ffmpeg
    
    fps = 10  # Use 10 fps (the default is 25fps).
    audio = ffmpeg.input("test/audios/1.mp3").audio
    image = ffmpeg.input("test/images/1.jpg", loop="1", framerate=fps).video
    
    ffmpeg_command = ffmpeg.output(image, audio, "test/test.mov", shortest=None, vcodec="mjpeg", pix_fmt='yuvj420p', acodec="mp3")
    ffmpeg_command.overwrite_output().run()
    

    For better quality and speed, we may avoid re-encoding using codec="copy" argument:

    ffmpeg_command = ffmpeg.output(image, audio, "test/test.mov", shortest=None, codec="copy")