File size: 2,246 Bytes
5278313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"""One place that knows how to run ffmpeg and how to report it failing.

Both the /analyze endpoint and the Stage Read pipeline shell out to ffmpeg, and both used to lose
the reason it failed: one discarded the pipes, the other caught everything and returned False. A
process that dies before main() then reports only a numeric exit status - on Windows 3221225794 is
0xC0000142, STATUS_DLL_INIT_FAILED, which says nothing about audio and everything about the
environment the service was started in.
"""
import os
import shutil
import subprocess


class FFmpegError(RuntimeError):
    """ffmpeg exited non-zero. The message carries its stderr where there was any."""


def ffmpeg_exe() -> str:
    """Absolute path to ffmpeg. Resolving it here beats relying on the PATH the service happened to
    inherit — a long-lived service can outlive the shell whose PATH it was started with."""
    exe = os.environ.get("STAGE_READ_FFMPEG") or shutil.which("ffmpeg")
    if not exe:
        raise FFmpegError("ffmpeg is not on PATH. Install it, or set STAGE_READ_FFMPEG to its full path.")
    return exe


def run_ffmpeg(args, timeout=None):
    """Run ffmpeg with the given arguments and, if it fails, say why.

    Passes -nostdin and an explicit DEVNULL: a service launched in the background can be holding a
    stdin that is no longer connected to anything, and ffmpeg reads stdin unless told not to.
    """
    exe = ffmpeg_exe()
    cmd = [exe, "-nostdin", "-y", "-v", "error", *args]
    r = subprocess.run(cmd, stdin=subprocess.DEVNULL, capture_output=True, timeout=timeout)
    if r.returncode == 0:
        return r

    err = (r.stderr or b"").decode("utf-8", "replace").strip()
    detail = err.splitlines()[-3:] if err else []
    status = r.returncode & 0xFFFFFFFF
    msg = f"ffmpeg failed (exit {r.returncode}, 0x{status:08X}) [{exe}]"
    if detail:
        msg += ": " + " | ".join(detail)
    elif status == 0xC0000142:
        msg += (": the process could not start (Windows STATUS_DLL_INIT_FAILED) - it produced no "
                "output at all. Restart the service; if it persists, set STAGE_READ_FFMPEG to a "
                "known-good ffmpeg.exe.")
    else:
        msg += ": no error output."
    raise FFmpegError(msg)