Skip to content

Add a niceness option to the FFmpeg class initializer #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions ffmpeg/asyncio/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion ffmpeg/asyncio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
30 changes: 29 additions & 1 deletion ffmpeg/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion ffmpeg/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down