From 3e938f20ccb065e98939f1d4e55679c328e25de1 Mon Sep 17 00:00:00 2001 From: Pavel Muhortov Date: Sun, 18 Jun 2023 07:55:58 +0300 Subject: [PATCH] add FFmpeg.probe() --- cctv-scheduler.py | 112 +++++++++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 30 deletions(-) diff --git a/cctv-scheduler.py b/cctv-scheduler.py index 6463e41..722c2d2 100644 --- a/cctv-scheduler.py +++ b/cctv-scheduler.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 +import json import logging import urllib.request from argparse import ArgumentParser @@ -1204,7 +1205,7 @@ class Proc: return execlist @classmethod - def list(cls) -> list: + def list_all(cls) -> list: """Find all running process. Returns: @@ -1230,7 +1231,7 @@ class Proc: """ proc_found = [] try: - for proc in cls.list(): + for proc in cls.list_all(): if exclude and ( exclude in proc['execpid'] or exclude in proc['exename'] or @@ -1272,34 +1273,43 @@ class FFmpeg: @classmethod def run( cls, - src: str, + src: (str, type(None)) = None, dst: str = None, fps: int = None, preset: str = None, + raw: (str, type(None)) = None, ffpath: str = None, watchdog: bool = False, watchsec: int = None, onlyonce: bool = False - ) -> None: - """Running the installed ffmpeg + ) -> int: + """Running the installed ffmpeg. Args: - src (str): sources urls (example: "rtsp://user:pass@host:554/Streaming/Channels/101, anull"). - dst (str, optional): destination url (example: rtp://239.0.0.1:5554). Defaults to None. + src (str, type, optional): sources urls, example: + 'rtsp://user:pass@host:554/Streaming/Channels/101, anull'. Defaults to None. + dst (str, optional): destination url, example: 'rtp://239.0.0.1:5554'. Defaults to None. fps (int, optional): frame per second encoding output. Defaults to None. preset (str, optional): 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p. Defaults to None. - ffpath (str, optional): alternative path to bin (example: /usr/bin/ffmpeg). Defaults to None. + raw (str, type, optional): custom ffmpeg parameters string. Defaults to None. + ffpath (str, optional): custom path to bin, example: /usr/bin/ffmpeg. Defaults to None. watchdog (bool, optional): detect ffmpeg freeze and terminate. Defaults to False. - watchsec (int, optional): seconds to wait before the watchdog terminates. Defaults to None. + watchsec (int, optional): seconds to wait before watchdog terminates. Defaults to None. onlyonce (bool, optional): detect ffmpeg running copy and terminate. Defaults to False. - """ - process = ( - cls._bin(ffpath).split() + - cls._src(src).split() + - cls._preset(preset, fps).split() + - cls._dst(dst).split() - ) + Returns: + int: ffmpeg return code + """ + if not raw: + process = ('' + + cls._bin(ffpath).split() + + cls._src(src).split() + + cls._preset(preset, fps).split() + + cls._dst(dst).split() + ) + else: + process = cls._bin(ffpath).split() + raw.split() + if onlyonce and Proc.search(' '.join(process)): print('Process already exist, exit...') else: @@ -1318,17 +1328,18 @@ class FFmpeg: print(line, flush=True) else: que.put(line) - exit() + return proc.returncode @classmethod - def _bin(cls, path_ffmpeg: str) -> str: - """Returns the path to the ffmpeg depending on the OS. + def _bin(cls, ffpath: str, tool: str = 'ffmpeg') -> str: + """Returns the path to the bin depending on the OS. Args: - path_ffmpeg (str): alternative path to bin. + ffpath (str): custom path to bin. + tool (str, optional): 'ffmpeg', 'ffprobe'. Defaults to 'ffmpeg'. Returns: - str: path to ffmpeg. + str: path to bin or None, if path does not exist. """ faq = ( '\n' @@ -1345,16 +1356,22 @@ class FFmpeg: 'Install on MacOS:\n' '\tDownload and extract archive from: https://evermeet.cx/ffmpeg/\n' '\tTarget: /usr/bin/ffmpeg\n' - ) - if not path_ffmpeg: + ) + if not ffpath: if platform.startswith('linux') or platform.startswith('darwin'): - path_ffmpeg = '/usr/bin/ffmpeg' + if tool == 'ffprobe': + ffpath = '/usr/bin/ffprobe' + else: + ffpath = '/usr/bin/ffmpeg' elif platform.startswith('win32'): - path_ffmpeg = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" - if path.exists(path_ffmpeg): - return path_ffmpeg + if tool == 'ffprobe': + ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffprobe.exe" + else: + ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" + if path.exists(ffpath): + return ffpath else: - print('ON', platform, 'PLATFORM', 'not found ffmpeg', faq) + print('ON', platform, 'PLATFORM', 'not found', tool, faq) return None @classmethod @@ -1471,6 +1488,41 @@ class FFmpeg: break exit() + @classmethod + def probe( + cls, + target: (str, type(None)) = None, + raw: (str, type(None)) = None, + ffpath: str = None + ) -> (dict, bytes, None): + """Running the installed ffprobe. + + Args: + target (str, type, optional): media file path to probe. Defaults to None. + raw (str, type, optional): custom ffprobe parameters string. Defaults to None. + ffpath (str, optional): custom path to bin, example: /usr/bin/ffprobe. Defaults to None. + + Returns: + dict, bytes, None: ffprobe response or None. + """ + if not raw: + command = ([] + + cls._bin(ffpath=ffpath, tool='ffprobe').split() + + ('-i ' + target + + ' -v quiet -print_format json -show_format -show_programs -show_streams').split() + ) + else: + command = cls._bin(ffpath=ffpath, tool='ffprobe').split() + raw.split() + + with Popen(command, stdout=PIPE, stderr=STDOUT) as process: + result = process.communicate() + if process.returncode == 0 and not raw: + return json.loads(result[0].decode('utf-8')) + elif process.returncode == 0 and raw: + return result[0] + else: + return None + if __name__ == "__main__": time_start = datetime.now() @@ -1556,7 +1608,7 @@ if __name__ == "__main__": watchsec = None if 'watchsec' in broadcast_config: watchsec = int(broadcast_config['watchsec']) - monopoly = None + onlyonce = None if 'onlyonce' in broadcast_config: onlyonce = broadcast_config['onlyonce'] FFmpeg.run( @@ -1568,7 +1620,7 @@ if __name__ == "__main__": watchdog=watchdog, watchsec=watchsec, onlyonce=onlyonce - ) + ) elif args['sequences']: logging.info(msg='Starting PTZ sequences from config file') sensors = {}