diff --git a/ffmpeg/asyncio/ffmpeg.py b/ffmpeg/asyncio/ffmpeg.py index 6bedec9..6e7ffb7 100644 --- a/ffmpeg/asyncio/ffmpeg.py +++ b/ffmpeg/asyncio/ffmpeg.py @@ -19,15 +19,25 @@ class FFmpeg(AsyncIOEventEmitter): - def __init__(self, executable: str = "ffmpeg"): - """Initialize an `FFmpeg` instance using `asyncio` + def __init__(self, executable: str = "ffmpeg", niceness: int = 0): + """Initialize an `FFmpeg` instance using `asyncio`. Args: executable: The path to the ffmpeg executable. Defaults to "ffmpeg". + niceness: The niceness of the ffmpeg process + (between -20 and 19, higher niceness is lower priority). + Defaults to 0 (no change). + On Windows, n < -10 is High priority, -10 <= n < 0 is Above Average, + 0 < n <= 10 is Below Average, and n > 10 is Idle. + On Linux, niceness below 0 requires root privileges. """ super().__init__() self._executable: str = executable + if isinstance(niceness, int) and -20 <= niceness <= 19: + self._niceness: int = niceness + else: + raise ValueError("Niceness must be an int between -20 and 19") self._options: Options = Options() self._process: asyncio.subprocess.Process @@ -174,11 +184,29 @@ async def execute( self.emit("start", self.arguments) + prio_args = {} + + if self._niceness != 0: + if is_windows(): + creationflags = 0 + if self._niceness < -10: + creationflags = subprocess.HIGH_PRIORITY_CLASS # type: ignore + elif self._niceness < 0: + creationflags = subprocess.ABOVE_NORMAL_PRIORITY_CLASS # type: ignore + elif self._niceness > 10: + creationflags = subprocess.IDLE_PRIORITY_CLASS # type: ignore + elif self._niceness > 0: + creationflags = subprocess.BELOW_NORMAL_PRIORITY_CLASS # type: ignore + prio_args["creationflags"] = creationflags + else: + prio_args["preexec_fn"] = lambda: os.nice(self._niceness) # type: ignore + self._process = await create_subprocess( *self.arguments, stdin=subprocess.PIPE if stream is not None else None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + **prio_args, ) self._executed = True diff --git a/ffmpeg/asyncio/utils.py b/ffmpeg/asyncio/utils.py index 959ea3f..ddaae5d 100644 --- a/ffmpeg/asyncio/utils.py +++ b/ffmpeg/asyncio/utils.py @@ -13,7 +13,9 @@ def create_subprocess(*args: Any, **kwargs: Any) -> Awaitable[asyncio.subprocess # which is required to gracefully terminate the FFmpeg process. # Reference: https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process.send_signal if is_windows(): - kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore + if "creationflags" not in kwargs: + kwargs["creationflags"] = 0 + kwargs["creationflags"] |= subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore return asyncio.create_subprocess_exec(*args, **kwargs) diff --git a/ffmpeg/ffmpeg.py b/ffmpeg/ffmpeg.py index 36a8c73..07617e1 100644 --- a/ffmpeg/ffmpeg.py +++ b/ffmpeg/ffmpeg.py @@ -18,15 +18,25 @@ class FFmpeg(EventEmitter): - def __init__(self, executable: str = "ffmpeg"): + def __init__(self, executable: str = "ffmpeg", niceness: int = 0): """Initialize an `FFmpeg` instance. Args: executable: The path to the ffmpeg executable. Defaults to "ffmpeg". + niceness: The niceness of the ffmpeg process + (between -20 and 19, higher niceness is lower priority). + Defaults to 0 (no change). + On Windows, n < -10 is High priority, -10 <= n < 0 is Above Average, + 0 < n <= 10 is Below Average, and n > 10 is Idle. + On Linux, niceness below 0 requires root privileges. """ super().__init__() self._executable: str = executable + if isinstance(niceness, int) and -20 <= niceness <= 19: + self._niceness: int = niceness + else: + raise ValueError("Niceness must be an int between -20 and 19") self._options: Options = Options() self._process: subprocess.Popen[bytes] @@ -169,12 +179,30 @@ def execute(self, stream: Optional[Union[bytes, IO[bytes]]] = None, timeout: Opt self.emit("start", self.arguments) + prio_args = {} + + if self._niceness != 0: + if is_windows(): + creationflags = 0 + if self._niceness < -10: + creationflags = subprocess.HIGH_PRIORITY_CLASS # type: ignore + elif self._niceness < 0: + creationflags = subprocess.ABOVE_NORMAL_PRIORITY_CLASS # type: ignore + elif self._niceness > 10: + creationflags = subprocess.IDLE_PRIORITY_CLASS # type: ignore + elif self._niceness > 0: + creationflags = subprocess.BELOW_NORMAL_PRIORITY_CLASS # type: ignore + prio_args["creationflags"] = creationflags + else: + prio_args["preexec_fn"] = lambda: os.nice(self._niceness) # type: ignore + self._process = create_subprocess( self.arguments, bufsize=0, stdin=subprocess.PIPE if stream is not None else None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + **prio_args, ) with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor: diff --git a/ffmpeg/utils.py b/ffmpeg/utils.py index ce70169..9d7f7ac 100644 --- a/ffmpeg/utils.py +++ b/ffmpeg/utils.py @@ -39,7 +39,9 @@ def create_subprocess(*args: Any, **kwargs: Any) -> subprocess.Popen: # which is required to gracefully terminate the FFmpeg process. # Reference: https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal if is_windows(): - kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore + if "creationflags" not in kwargs: + kwargs["creationflags"] = 0 + kwargs["creationflags"] |= subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore return subprocess.Popen(*args, **kwargs)