Search code examples
audiovideoffmpegsubprocess

ffmpeg with python subprocess Popen


I'm trying to record webcam video from python using ffmpeg. The following ffmpeg works properly when run from the cmd

ffmpeg -f dshow -rtbufsize 2000M -i video="HP HD Camera":audio="Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)" -y -vcodec libx264 -crf 24 output.mp4 

I have checked the audio and video inputs with

ffmpeg -list_devices true -f dshow -i dummy

Now when I try to call the command using subprocess problem happens. I can just pass the whole command like this

import subprocess, time
recorder = subprocess.Popen('ffmpeg -f dshow -rtbufsize 2000M -i video="HP HD Camera":audio="Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)" -y -vcodec libx264 -crf 24 output.mp4', shell=True)
time.sleep(10)
recorder.terminate()
recorder.kill()

Now, as this is run with shell=True this won't terminate the recording and I have checked different solution, but they won't work either. So, I opted for shell=False. I tried with

recorder = subprocess.Popen(['ffmpeg', '-f', 'dshow', '-rtbufsize', '2000M', '-t', '60', '-i', 'video="HP HD Camera":audio="Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)"', '-y', '-vcodec', 'libx264', '-crf', '24', 'output.mp4'],shell=False)

This throws the error

[dshow @ 0000024f2becd540] Could not find video device with name ["HP HD Camera"] among source devices of type video.
video="HP HD Camera":audio="Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)": I/O error

Then, I tried

recorder = subprocess.Popen(['ffmpeg', '-f', 'dshow', '-rtbufsize', '2000M', '-t', '60', '-i', 'video=HP HD Camera:audio=Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)', '-y', '-vcodec', 'libx264', '-crf', '24', 'output.mp4'],shell=False)

This creates the output, but the video is not playable.
How to fix this?


Solution

  • We may add double quotes for letting Python subprocess know the string is one argument, and we have to close FFmpeg gracefully.

    Adding double quotes:
    Replace the following part video="HP HD Camera":audio="Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)" with:
    "video=""HP HD Camera"":audio=""Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)"""
    Doubling the quotes "" is required because " is between quotes " ... ".

    We may also use it without the "": "video=HP HD Camera:audio=Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)".


    For getting a playable file, we have to close FFmpeg gracefully.
    For simply recording 10 seconds, we may add -t 10 argument.

    For closing FFmpeg gracefully programmatically, we may write 'q'.encode("GBK") to stdin pipe of FFmpeg sub-process.
    Writing q simulates that q key is pressed by the user (stop recording).

    • Add stdin=subprocess.PIPE argument to subprocess.Popen.

    • After time.sleep(10) add:

       recorder.stdin.write('q'.encode("GBK"))  # Simulate user pressing q key
       recorder.communicate()
      
    • Add recorder.wait() (instead of recorder.terminate() and recorder.kill()).


    As good practice we may also add shlex.split(...) for converting the command line to a list of arguments.
    In Windows it supposed to work without it, but in Linux it's not.

    Code sample:

    import subprocess, time, shlex
    
    recorder = subprocess.Popen(shlex.split('ffmpeg -f dshow -rtbufsize 2000M -t 60 -i """video=HP HD Camera:audio=Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)"":""audio=Headset (realme Buds Wireless 2 Neo Hands-Free AG Audio)"""'
                                            ' -y -vcodec libx264 -crf 24 output.mp4'), stdin=subprocess.PIPE, shell=False)
    
    time.sleep(10)
    
    recorder.stdin.write('q'.encode("GBK"))  # Simulate user pressing q key
    recorder.communicate()
    recorder.wait()