Python调用FFmpeg,Pyinstaller打包exe运行时弹出CMD控制台黑框解决办法

问题描述

  • Windows10平台PyInstaller打包exe

用Python和PyQt写了一个小工具,其中其中应用ffmpy调用ffmpeg进行了RGB转YUV444,计算PSNR和SSIM。

代码如下:
RGB转YUV444

def convertYUV444(img_path,H,W,output_path):
    size = '{}x{}'.format(W,H)
    ff = FFmpeg(inputs={img_path:None},
                outputs={output_path:'-y -s {} -pix_fmt yuv444p'.format(size)})
    print(ff.cmd)
    ff.run()

如有同名输出文件存在,-y指令将实现覆盖,否则会报文件已存在的错。print(ff.cmd)会将当前执行指令打印出来,便于查看指令是否存在错误。

计算PSNR

def calculatePSNR(img_path1,img_path2,H,W):
    size = '{}x{}'.format(W,H)
    log_file = 'stats_file=psnr.log'
    ff = FFmpeg(inputs={img_path1:"-s {} -pix_fmt yuv444p".format(size), img_path2:"-s {} -pix_fmt yuv444p".format(size)},
                outputs={None:"-lavfi psnr={} -f null -".format(log_file)})
    print(ff.cmd)
    ff.run()
    return psnr_log

计算SSIM

def calculateSSIM(img_path1,img_path2,H,W):
    size = '{}x{}'.format(W,H)
    ssim_log = doc_img_path.split('\\')[-1][:-4]+'_ssim.log'
    # print(ssim_log)
    log_file = 'stats_file=ssim.log'
    ff = FFmpeg(inputs={img_path1:"-s {} -pix_fmt yuv444p".format(size), img_path2:"-s {} -pix_fmt yuv444p".format(size)},
                outputs={None:"-lavfi ssim={} -f null -".format(log_file)})
    print(ff.cmd)
    ff.run()
    return ssim_log

调用上述函数时,会发现CMD控制台持续在输出信息,且输出的出了print(ff.cmd)的信息以外,还有很多log信息被打印出来。

PyInstaller打包后,运行exe在执行代码的过程中,一直弹出CMD控制台黑框,导致exe运行虽然在进行但进入无响应状态,且黑框的持续弹出会影响使用其他程序。

抑制输出(静默运行)

第一步是先实现抑制输出(静默运行),ffmpeg在运行时不打印log信息。三个函数均加入-v -8参数,注意一下添加的位置。

RGB转YUV444

def convertYUV444(img_path,H,W,output_path):
    size = '{}x{}'.format(W,H)
    ff = FFmpeg(inputs={img_path:None},
                outputs={output_path:'-y -s {} -pix_fmt yuv444p -v-8'.format(size)})
    print(ff.cmd)
    ff.run()

计算PSNR

def calculatePSNR(img_path1,img_path2,H,W):
    size = '{}x{}'.format(W,H)
    log_file = 'stats_file=psnr.log'
    ff = FFmpeg(inputs={img_path1:"-s {} -pix_fmt yuv444p".format(size), img_path2:"-s {} -pix_fmt yuv444p".format(size)},
                outputs={None:"-v -8 -lavfi psnr={} -f null -".format(log_file)})
    print(ff.cmd)
    ff.run()
    return psnr_log

计算SSIM

def calculateSSIM(img_path1,img_path2,H,W):
    size = '{}x{}'.format(W,H)
    ssim_log = doc_img_path.split('\\')[-1][:-4]+'_ssim.log'
    # print(ssim_log)
    log_file = 'stats_file=ssim.log'
    ff = FFmpeg(inputs={img_path1:"-s {} -pix_fmt yuv444p".format(size), img_path2:"-s {} -pix_fmt yuv444p".format(size)},
                outputs={None:"-v -8 -lavfi ssim={} -f null -".format(log_file)})
    print(ff.cmd)
    ff.run()
    return ssim_log

这时候再运行exe,log信息不再会被打印,但是CMD控制台黑框依然没有消失,还是持续弹出。

彻底抑制CMD黑框弹出

参考了这一篇文章 https://blog.csdn.net/zj520077/article/details/103849967

但我的情况是用ffmpy调用ffmpeg,无法找到与上文中完全相应的文件。我使用了一个虚拟环境pipenv进行打包,在C:\Users\username.virtualenvs找到创建的环境里的\Lib\site-packages\ffmpy.py. 找到class FFmpeg中的run函数,将其中

self.process = subprocess.Popen(
                self._cmd,  stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, env=env
            )

改为

self.process = subprocess.Popen(
                self._cmd,  shell=True, stdin=subprocess.PIPE, 
                stdout=stdout, stderr=stderr, env=env
            )
class FFmpeg(object):
    """Wrapper for various `FFmpeg <https://www.ffmpeg.org/>`_ related applications (ffmpeg,
    ffprobe).
    """

    def __init__(
        self, executable="ffmpeg", global_options=None, inputs=None, outputs=None
    ):
        """Initialize FFmpeg command line wrapper.

        Compiles FFmpeg command line from passed arguments (executable path, options, inputs and
        outputs). ``inputs`` and ``outputs`` are dictionares containing inputs/outputs as keys and
        their respective options as values. One dictionary value (set of options) must be either a
        single space separated string, or a list or strings without spaces (i.e. each part of the
        option is a separate item of the list, the result of calling ``split()`` on the options
        string). If the value is a list, it cannot be mixed, i.e. cannot contain items with spaces.
        An exception are complex FFmpeg command lines that contain quotes: the quoted part must be
        one string, even if it contains spaces (see *Examples* for more info).
        For more info about FFmpeg command line format see `here
        <https://ffmpeg.org/ffmpeg.html#Synopsis>`_.

        :param str executable: path to ffmpeg executable; by default the ``ffmpeg`` command will be
            searched for in the ``PATH``, but can be overridden with an absolute path to ``ffmpeg``
            executable
        :param iterable global_options: global options passed to ``ffmpeg`` executable (e.g.
            ``-y``, ``-v`` etc.); can be specified either as a list/tuple/set of strings, or one
            space-separated string; by default no global options are passed
        :param dict inputs: a dictionary specifying one or more input arguments as keys with their
            corresponding options (either as a list of strings or a single space separated string) as
            values
        :param dict outputs: a dictionary specifying one or more output arguments as keys with their
            corresponding options (either as a list of strings or a single space separated string) as
            values
        """
        self.executable = executable
        self._cmd = [executable]

        global_options = global_options or []
        if _is_sequence(global_options):
            normalized_global_options = []
            for opt in global_options:
                normalized_global_options += shlex.split(opt)
        else:
            normalized_global_options = shlex.split(global_options)

        self._cmd += normalized_global_options
        self._cmd += _merge_args_opts(inputs, add_input_option=True)
        self._cmd += _merge_args_opts(outputs)

        self.cmd = subprocess.list2cmdline(self._cmd)
        self.process = None

    def __repr__(self):
        return "<{0!r} {1!r}>".format(self.__class__.__name__, self.cmd)

    def run(self, input_data=None, stdout=None, stderr=None, env=None):
        """Execute FFmpeg command line.

        ``input_data`` can contain input for FFmpeg in case ``pipe`` protocol is used for input.
        ``stdout`` and ``stderr`` specify where to redirect the ``stdout`` and ``stderr`` of the
        process. By default no redirection is done, which means all output goes to running shell
        (this mode should normally only be used for debugging purposes). If FFmpeg ``pipe`` protocol
        is used for output, ``stdout`` must be redirected to a pipe by passing `subprocess.PIPE` as
        ``stdout`` argument. You can pass custom environment to ffmpeg process with ``env``.

        Returns a 2-tuple containing ``stdout`` and ``stderr`` of the process. If there was no
        redirection or if the output was redirected to e.g. `os.devnull`, the value returned will
        be a tuple of two `None` values, otherwise it will contain the actual ``stdout`` and
        ``stderr`` data returned by ffmpeg process.

        More info about ``pipe`` protocol `here <https://ffmpeg.org/ffmpeg-protocols.html#pipe>`_.

        :param str input_data: input data for FFmpeg to deal with (audio, video etc.) as bytes (e.g.
            the result of reading a file in binary mode)
        :param stdout: redirect FFmpeg ``stdout`` there (default is `None` which means no
            redirection)
        :param stderr: redirect FFmpeg ``stderr`` there (default is `None` which means no
            redirection)
        :param env: custom environment for ffmpeg process
        :return: a 2-tuple containing ``stdout`` and ``stderr`` of the process
        :rtype: tuple
        :raise: `FFRuntimeError` in case FFmpeg command exits with a non-zero code;
            `FFExecutableNotFoundError` in case the executable path passed was not valid
        """
        try:
            self.process = subprocess.Popen(
                self._cmd, shell=True, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, env=env
            )
        except OSError as e:
            if e.errno == errno.ENOENT:
                raise FFExecutableNotFoundError(
                    "Executable '{0}' not found".format(self.executable)
                )
            else:
                raise

        out = self.process.communicate(input=input_data)
        if self.process.returncode != 0:
            raise FFRuntimeError(self.cmd, self.process.returncode, out[0], out[1])

        return out

这时候再打包出来的exe就完全没有黑框的困扰了。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值