fix: Do not create command prompt window on subprocess (#436)

* fix: Do not create command prompt window on subcmd

Patches files from abandoned libraries are located and updated in
src/qt/helpers/vendored with modified sections labeld PATCHED. A wrapper
around subprocess.Popen automatically sets the creation flag to no
window on windows.

* fix: Replace Popen in mediainfo_json decoder

* fixup: Pipe stdin to stdin

* chore: Exclude vendored dir from tooling checks

* suppress mypy warnings
This commit is contained in:
Sean Krueger
2024-09-02 23:40:52 -07:00
committed by GitHub
parent 85b6d9decc
commit fc714e02e6
7 changed files with 1596 additions and 4 deletions

View File

@@ -1,9 +1,9 @@
[tool.ruff]
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py", "**/vendored/"]
[tool.mypy]
strict_optional = false
disable_error_code = ["union-attr", "annotation-unchecked", "import-untyped"]
explicit_package_bases = true
warn_unused_ignores = true
exclude = ['tests']
exclude = ['tests', 'src/qt/helpers/vendored']

View File

@@ -6,6 +6,8 @@
import ffmpeg
from pathlib import Path
from src.qt.helpers.vendored.ffmpeg import _probe
def is_readable_video(filepath: Path | str):
"""Test if a video is in a readable format. Examples of unreadable videos
@@ -15,7 +17,7 @@ def is_readable_video(filepath: Path | str):
filepath (Path | str):
"""
try:
probe = ffmpeg.probe(Path(filepath))
probe = _probe(Path(filepath))
for stream in probe["streams"]:
# DRM check
if stream.get("codec_tag_string") in [

View File

@@ -0,0 +1,64 @@
import subprocess
import sys
def promptless_Popen(
args,
bufsize=-1,
executable=None,
stdin=None,
stdout=None,
stderr=None,
preexec_fn=None,
close_fds=True,
shell=False,
cwd=None,
env=None,
universal_newlines=None,
startupinfo=None,
restore_signals=True,
start_new_session=False,
pass_fds=(),
*,
group=None,
extra_groups=None,
user=None,
umask=-1,
encoding=None,
errors=None,
text=None,
pipesize=-1,
process_group=None,
):
creation_flags = 0
if sys.platform == "win32":
creation_flags = subprocess.CREATE_NO_WINDOW
return subprocess.Popen(
args=args,
bufsize=bufsize,
executable=executable,
stdin=stdin,
stdout=stdout,
stderr=stderr,
preexec_fn=preexec_fn,
close_fds=close_fds,
shell=shell,
cwd=cwd,
env=env,
universal_newlines=universal_newlines,
startupinfo=startupinfo,
creationflags=creation_flags,
restore_signals=restore_signals,
start_new_session=start_new_session,
pass_fds=pass_fds,
group=group,
extra_groups=extra_groups,
user=user,
umask=umask,
encoding=encoding,
errors=errors,
text=text,
pipesize=pipesize,
process_group=process_group,
)

View File

@@ -0,0 +1,34 @@
# Copyright (C) 2022 Karl Kroening (kkroening).
# Licensed under the GPL-3.0 License.
# Vendored from ffmpeg-python and ffmpeg-python PR#790 by amamic1803
import subprocess
import json
import sys
import ffmpeg
from src.qt.helpers.silent_popen import promptless_Popen
def _probe(filename, cmd='ffprobe', timeout=None, **kwargs):
"""Run ffprobe on the specified file and return a JSON representation of the output.
Raises:
:class:`ffmpeg.Error`: if ffprobe returns a non-zero exit code,
an :class:`Error` is returned with a generic error message.
The stderr output can be retrieved by accessing the
``stderr`` property of the exception.
"""
args = [cmd, '-show_format', '-show_streams', '-of', 'json']
args += ffmpeg._utils.convert_kwargs_to_cmd_line_args(kwargs)
args += [filename]
# PATCHED
p = promptless_Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
communicate_kwargs = {}
if timeout is not None:
communicate_kwargs['timeout'] = timeout
out, err = p.communicate(**communicate_kwargs)
if p.returncode != 0:
raise ffmpeg.Error('ffprobe', out, err)
return json.loads(out.decode('utf-8'))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
import json
import re
import subprocess
from pydub.utils import (
get_prober_name,
fsdecode,
_fd_or_path_or_tempfile,
get_extra_info,
)
from src.qt.helpers.silent_popen import promptless_Popen
def _mediainfo_json(filepath, read_ahead_limit=-1):
"""Return json dictionary with media info(codec, duration, size, bitrate...) from filepath
"""
prober = get_prober_name()
command_args = [
"-v", "info",
"-show_format",
"-show_streams",
]
try:
command_args += [fsdecode(filepath)]
stdin_parameter = None
stdin_data = None
except TypeError:
if prober == 'ffprobe':
command_args += ["-read_ahead_limit", str(read_ahead_limit),
"cache:pipe:0"]
else:
command_args += ["-"]
stdin_parameter = subprocess.PIPE
file, close_file = _fd_or_path_or_tempfile(filepath, 'rb', tempfile=False)
file.seek(0)
stdin_data = file.read()
if close_file:
file.close()
command = [prober, '-of', 'json'] + command_args
# PATCHED
res = promptless_Popen(command, stdin=stdin_parameter, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, stderr = res.communicate(input=stdin_data)
output = output.decode("utf-8", 'ignore')
stderr = stderr.decode("utf-8", 'ignore')
try:
info = json.loads(output)
except json.decoder.JSONDecodeError:
# If ffprobe didn't give any information, just return it
# (for example, because the file doesn't exist)
return None
if not info:
return info
extra_info = get_extra_info(stderr)
audio_streams = [x for x in info['streams'] if x['codec_type'] == 'audio']
if len(audio_streams) == 0:
return info
# We just operate on the first audio stream in case there are more
stream = audio_streams[0]
def set_property(stream, prop, value):
if prop not in stream or stream[prop] == 0:
stream[prop] = value
for token in extra_info[stream['index']]:
m = re.match(r'([su]([0-9]{1,2})p?) \(([0-9]{1,2}) bit\)$', token)
m2 = re.match(r'([su]([0-9]{1,2})p?)( \(default\))?$', token)
if m:
set_property(stream, 'sample_fmt', m.group(1))
set_property(stream, 'bits_per_sample', int(m.group(2)))
set_property(stream, 'bits_per_raw_sample', int(m.group(3)))
elif m2:
set_property(stream, 'sample_fmt', m2.group(1))
set_property(stream, 'bits_per_sample', int(m2.group(2)))
set_property(stream, 'bits_per_raw_sample', int(m2.group(2)))
elif re.match(r'(flt)p?( \(default\))?$', token):
set_property(stream, 'sample_fmt', token)
set_property(stream, 'bits_per_sample', 32)
set_property(stream, 'bits_per_raw_sample', 32)
elif re.match(r'(dbl)p?( \(default\))?$', token):
set_property(stream, 'sample_fmt', token)
set_property(stream, 'bits_per_sample', 64)
set_property(stream, 'bits_per_raw_sample', 64)
return info

View File

@@ -27,7 +27,8 @@ from PIL import (
)
from PIL.Image import DecompressionBombError
from pillow_heif import register_avif_opener, register_heif_opener
from pydub import AudioSegment, exceptions
from pydub import exceptions
from src.qt.helpers.vendored.pydub.audio_segment import _AudioSegment as AudioSegment # type: ignore
from PySide6.QtCore import QObject, QSize, Qt, Signal
from PySide6.QtGui import QGuiApplication, QPixmap
from src.core.constants import FONT_SAMPLE_SIZES, FONT_SAMPLE_TEXT