20

After terminating an ffmpeg subprocess, the terminal gets messed up - typed characters are invisible! The input still works in that commands can be executed, but keyboard input is not echoed to the terminal.

Issuing shell command reset puts everything back to normal (or !reset from within ipython), so a workaround the issue is calling os.system('reset') inside the script.

Other things I've tried: import curses; curses.initscr() before spawning the subprocess and curses.endwin() after termination, which worked somewhat but broke other stuff. Another possibly related issue is that after spawning the child process, the interactive terminal becomes laggy and sometimes fails to capture typed characters.

The code to spawn the process looks like:

with open('/tmp/stdout.log', 'w') as o:
    with open('/tmp/stderr.log', 'w') as e:
        proc = subprocess.Popen([args], stdout=o, stderr=e)

And later to stop it:

proc.terminate()
proc.communicate()

What could be going wrong here?

wim
  • 338,267
  • 99
  • 616
  • 750
  • i guess it has smth to do with `stdout=o, stderr=e` – warvariuc Jun 27 '11 at 05:57
  • The same problem exists in django runserver when it reloads. – lprsd Jun 27 '11 at 06:57
  • i have redirected stdout and stderr to os.devnull and the problem persists – wim Jun 27 '11 at 07:50
  • i think you redirect the output and it doesn't come back after the process terminates. – warvariuc Jun 27 '11 at 15:43
  • 1
    something changes terminal settings and doesn't restore them. If it is your code; you could [use a context manager to restore the terminal whether an error occurs or not](http://stackoverflow.com/a/327072/4279). If it is the subprocess (`ffmpeg`) then you could try to shutdown it more gently (as your answer tries) e.g., `proc = Popen(..., stdin=PIPE); ...; proc.stdin.write("q")/.close()`. – jfs Oct 31 '13 at 12:13

6 Answers6

23

Change the script so that proc.terminate() is not used. You can stop an ffmpeg subprocess more politely with

  proc.send_signal(signal.SIGINT)
  proc.wait()

This allows ffmpeg the chance to write whatever escape sequences it needs to restore the terminal.


edit: discovered later- another tip to make ffmpeg behave better with Popen is to provide it a subprocess.PIPE or open(os.devnull) in the stdin handle. Otherwise, it seems to try to get input from the parent's stdin which can cause weird terminal behaviour. A running ffmpeg process is listening for '?' and 'q' input on stdin.

wim
  • 338,267
  • 99
  • 616
  • 750
  • I was running a subprocess using sudo, so I did not have permission to send SIGINT. For me it was sufficient to do: `proc.stdin.close; proc.wait()` – jdhildeb May 08 '18 at 17:43
8

As stated in this answer, ffmpeg expects data from stdin. You can run ffmpeg with the -nostdin flag and it will keep your terminal from hiding keystrokes.

Ian Hunter
  • 9,466
  • 12
  • 61
  • 77
3

os.system('stty sane') worked for me. It reset settings making echo invisible.

Quentin Engles
  • 2,744
  • 1
  • 20
  • 33
2

do you communicate with the subprocess? in that case i would use pexpect which makes that type of setup very simple, perhaps you must wait for the command to finish? i.e.

 p = subprocess.Popen(argv, stdout=o, stderr=e)
 p.wait()
 if p.returncode != 0:
      print("problems")

that's what i use on a dvd2h264 script i wrote a while back, never had any problems with it, but i don't redirect stdin/stderr to tmpfiles..

bjarneh
  • 608
  • 5
  • 14
  • there is not really interactive communication - i'm encoding a live stream piped in from a capture card, and the Popen instance is terminated when a the script receives a SIGINT (either from keyboard, or from another script). i have tried also using proc.send_signal(signal.SIGINT) , and waiting half a second before checking proc.returncode .. if it's still None then i kill it with terminate() which i think uses a SIGTERM – wim Jul 07 '11 at 08:21
  • ok, are you able to extract the code that fails, i.e. make an `hallo fail` example that i could try out? the cause of the failed terminal must be that an escape sequence is written to stdout, but in order to see what happens a tiny code fragment that produces the error would help. – bjarneh Jul 09 '11 at 07:02
  • thanks, i have seen cases where you can write in coloured text using escape sequences , so you have given me some ideas to try - maybe i need to 'unsescape' back to plain text. unfortunately i can not provide you with a consistently failing example , because it seems to be an intermittent problem and you might possibly need the same capture card to reproduce it – wim Jul 10 '11 at 23:54
  • hi, yes it's all escape sequences, to reset the terminal could be done by something like this, `sys.stdout.write(curses.tigetstr('sgr0'))` – bjarneh Jul 11 '11 at 11:44
0

How about:

 try:
     p = subprocess.Popen(argv, stdout=o, stderr=e)
 except KeyboardInterrupt:
     p.send_signal(signal.SIGINT)
     p.wait()
ruanhao
  • 4,663
  • 6
  • 28
  • 43
0

10 years later, and just ran into the same problem (Mac, Python 3.8.2).

Per this SO answer, I switched to using Popen as a context manager, and this ensured that ffmpeg was correctly releasing any of its handles.

def raw_start_times(in_filename, silence_threshold, silence_duration, start_ms = 0, end_ms = 200 * 1000):
    """Given an in_filename, find possible split points (phrase start
    times) using ffmpeg.

    Note that potential phrase start times are actually when any
    silence in the clip *ends*.
    """

    timematch = r'(?P<deltafromstart>[0-9]+(\.?[0-9]*))'
    end_re = re.compile(f'silence_end: {timematch} ')

    # The time returned is the deltafromstart; i.e., the actual
    # time is the start_ms + the delta.
    def time_ms(m):
        return start_ms + round(float(m.group('deltafromstart')) * 1000)

    # ffmpeg outputs e.g. "silence_end: 123.234" to stderr.
    def add_if_matches_end_re(line, arr):
        s = line.decode('utf-8').strip()
        end_match = end_re.search(s)
        if end_match:
            arr.append(time_ms(end_match))

    ffmpegcmd = (
        ffmpeg
        .input(in_filename, ss=(start_ms/1000.0), t=(end_ms-start_ms)/1000.0)
        .filter('silencedetect', n='{}dB'.format(silence_threshold), d=silence_duration)
        .output('-', format='null')
        .compile()
    ) + ['-nostats']  # FIXME: use .nostats() once it's implemented in ffmpeg-python.
    logger.debug(f'Running command: {subprocess.list2cmdline(ffmpegcmd)}')

    chunk_starts = [start_ms]
    with subprocess.Popen(
            ffmpegcmd,
            stderr=subprocess.PIPE,
            stdout = subprocess.PIPE) as p:
        for line in p.stderr:
            add_if_matches_end_re(line, chunk_starts)
    return chunk_starts

J.Z.
  • 882
  • 9
  • 13