def check_ffmpeg(): if not os.path.exists("ffmpeg.exe"): exe_path = imageio_ffmpeg.get_ffmpeg_exe() ext = os.path.splitext(exe_path)[-1] filename = "ffmpeg" + ext shutil.copy(exe_path, filename)
def __init__( self, path: typing.Union[str, os.PathLike], pre_load: bool = None, fps: int = None, *_, **__, ): assert os.path.isfile(path), f"video {path} not existed" self.path: str = str(path) self.data: typing.Optional[typing.Tuple[VideoFrame]] = tuple() self._hook_list: typing.List["BaseHook"] = [] self.fps: int = fps if fps: video_path = os.path.join(tempfile.mkdtemp(), f"tmp_{fps}.mp4") logger.debug(f"convert video, and bind path to {video_path}") toolbox.fps_convert(fps, self.path, video_path, imageio_ffmpeg.get_ffmpeg_exe()) self.path = video_path with toolbox.video_capture(self.path) as cap: self.frame_count = toolbox.get_frame_count(cap) self.frame_size = toolbox.get_frame_size(cap) if pre_load is not None: logger.warning( f"`pre_load` has been deprecated. use `video.load_frames()` instead" ) logger.info( f"video object generated, length: {self.frame_count}, size: {self.frame_size}" )
def concat_videos(video_paths: List[str], output_path: str) -> str: """Use ffmpeg to concatenate a list of videos (with the same encoding) into a single video. Parameters ---------- video_paths : List[str] A list of paths (str) for videos that should be concatenated together Order of paths matters! output_path : str The desired output path for the concatenated video Returns ------- str Path of concatenated video """ with tempfile.NamedTemporaryFile(suffix=".txt") as concat_list_file: with open(concat_list_file.name, 'w') as fp: for path in video_paths: fp.write(f"file '{path}'\n") # See: https://trac.ffmpeg.org/wiki/Concatenate ffmpeg_concat_cmd = [ mpg.get_ffmpeg_exe(), '-y', '-f', 'concat', '-safe', '0', '-i', concat_list_file.name, '-c', 'copy', output_path ] subprocess.run(ffmpeg_concat_cmd) return output_path
def __init__(self, output_path, frame_shape, frames_per_sec, output_frames_per_sec): self.proc = None self.output_path = output_path # Frame shape should be lines-first, so w and h are swapped h, w, pixfmt = frame_shape if pixfmt != 3 and pixfmt != 4: raise error.InvalidFrame( "Your frame has shape {}, but we require (w,h,3) or (w,h,4), i.e., RGB values for a w-by-h image, with an optional alpha channel." .format(frame_shape)) self.wh = (w, h) self.includes_alpha = pixfmt == 4 self.frame_shape = frame_shape self.frames_per_sec = frames_per_sec self.output_frames_per_sec = output_frames_per_sec if distutils.spawn.find_executable("avconv") is not None: self.backend = "avconv" elif distutils.spawn.find_executable("ffmpeg") is not None: self.backend = "ffmpeg" elif pkgutil.find_loader("imageio_ffmpeg"): import imageio_ffmpeg self.backend = imageio_ffmpeg.get_ffmpeg_exe() else: raise error.DependencyNotInstalled( """Found neither the ffmpeg nor avconv executables. On OS X, you can install ffmpeg via `brew install ffmpeg`. On most Ubuntu variants, `sudo apt-get install ffmpeg` should do it. On Ubuntu 14.04, however, you'll need to install avconv with `sudo apt-get install libav-tools`. Alternatively, please install imageio-ffmpeg with `pip install imageio-ffmpeg`""" ) self.start()
def mux_audio(self): """ Mux audio ImageIO is a useful lib for frames > video as it also packages the ffmpeg binary however muxing audio is non-trivial, so this is done afterwards with ffmpy. A future fix could be implemented to mux audio with the frames """ logger.info("Muxing Audio...") exe = im_ffm.get_ffmpeg_exe() inputs = OrderedDict([(self.video_tmp_file, None), (self.source_video, None)]) outputs = {self.video_file: "-map 0:0 -map 1:1 -c: copy"} ffm = FFmpeg(executable=exe, global_options="-hide_banner -nostats -v 0 -y", inputs=inputs, outputs=outputs) logger.debug("Executing: %s", ffm.cmd) # Sometimes ffmpy exits for no discernible reason, but then works on a later attempt, # so take 5 shots at this attempts = 5 for attempt in range(attempts): logger.debug("Muxing attempt: %s", attempt + 1) try: ffm.run() except FFRuntimeError as err: logger.debug("ffmpy runtime error: %s", str(err)) if attempt != attempts - 1: continue logger.error("There was a problem muxing audio. The output video has been " "created but you will need to mux audio yourself either with the " "EFFMpeg tool or an external application.") os.rename(self.video_tmp_file, self.video_file) break logger.debug("Removing temp file") if os.path.isfile(self.video_tmp_file): os.remove(self.video_tmp_file)
def __init__(self, uri): probe = ffmpeg.probe(uri) video_info = next(s for s in probe['streams'] if s['codec_type'] == 'video') self.width = int(video_info['width']) self.height = int(video_info['height']) self.num_frames = int(video_info['nb_frames']) self.stream = ffmpeg.input(uri) self.executable = imageio_ffmpeg.get_ffmpeg_exe()
def _get_ffmpeg_path(): err = os.system("ffmpeg -version") if err == 0: # ffmpeg in path ffmpeg = "ffmpeg" else: # not in Path import imageio_ffmpeg ffmpeg = imageio_ffmpeg.get_ffmpeg_exe() ffmpeg = '\"' + ffmpeg + '\"' return ffmpeg
def ffcmd(args=""): ffpath = imageio_ffmpeg.get_ffmpeg_exe() cmd = ffpath + " " + args print("ffmpeg: ", cmd) with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as p: output, errors = p.communicate() lines = output.decode('utf-8').splitlines() if len(lines) != 0: print(lines) print("ffmpeg: Done!")
def __init__(self, arguments): logger.debug("Initializing %s: (arguments: %s)", self.__class__.__name__, arguments) self.args = arguments self.exe = im_ffm.get_ffmpeg_exe() self.input = DataItem() self.output = DataItem() self.ref_vid = DataItem() self.start = "" self.end = "" self.duration = "" self.print_ = False logger.debug("Initialized %s", self.__class__.__name__)
def __init__(self, arguments): logger.debug("Initializing %s: (arguments: %s)", self.__class__.__name__, arguments) self.args = arguments self.exe = im_ffm.get_ffmpeg_exe() self.input = DataItem() self.output = DataItem() self.ref_vid = DataItem() self.start = "" self.end = "" self.duration = "" self.print_ = False logger.debug("Initialized %s", self.__class__.__name__)
def __init__(self, filename: str, **kwargs) -> None: self._reader = None super().__init__(filename=filename, **kwargs) self._reader = imageio.get_reader(filename) if self._reader is None: LOG.error("Opening movie file (%s) failed", filename) raise RuntimeError("Creating video reader object for file " f"'{filename}' failed.") self._meta = self._reader.get_meta_data() self._index = -1 LOG.debug("Reader object: %r", self._reader) LOG.info("Video file: %s", filename) LOG.info("FFMPEG backend version %s (%s)", imageio_ffmpeg.get_ffmpeg_version(), imageio_ffmpeg.get_ffmpeg_exe())
def __init__( self, output_path: str, frame_shape: tuple[int, int, int], frames_per_sec: int, output_frames_per_sec: int, ): """Encoder for capturing image based frames of environment for Video Recorder. Args: output_path: The output data path frame_shape: The expected frame shape, a tuple of height, weight and channels (3 or 4) frames_per_sec: The number of frames per second the environment runs at output_frames_per_sec: The output number of frames per second for the video """ self.proc = None self.output_path = output_path # Frame shape should be lines-first, so w and h are swapped h, w, pixfmt = frame_shape if pixfmt != 3 and pixfmt != 4: raise error.InvalidFrame( f"Your frame has shape {frame_shape}, but we require (w,h,3) or (w,h,4), " "i.e., RGB values for a w-by-h image, with an optional alpha channel." ) self.wh = (w, h) self.includes_alpha = pixfmt == 4 self.frame_shape = frame_shape self.frames_per_sec = frames_per_sec self.output_frames_per_sec = output_frames_per_sec if shutil.which("avconv") is not None: self.backend = "avconv" elif shutil.which("ffmpeg") is not None: self.backend = "ffmpeg" elif pkgutil.find_loader("imageio_ffmpeg"): import imageio_ffmpeg self.backend = imageio_ffmpeg.get_ffmpeg_exe() else: raise error.DependencyNotInstalled( "Found neither the ffmpeg nor avconv executables. " "On OS X, you can install ffmpeg via `brew install ffmpeg`. " "On most Ubuntu variants, `sudo apt-get install ffmpeg` should do it. " "On Ubuntu 14.04, however, you'll need to install avconv with `sudo apt-get install libav-tools`. " "Alternatively, please install imageio-ffmpeg with `pip install imageio-ffmpeg`" ) self.start()
def __run_ffmpeg(exe=im_ffm.get_ffmpeg_exe(), inputs=None, outputs=None): """ Run ffmpeg """ logger.debug("Running ffmpeg: (exe: '%s', inputs: %s, outputs: %s", exe, inputs, outputs) ffm = FFmpeg(executable=exe, inputs=inputs, outputs=outputs) try: ffm.run(stderr=subprocess.STDOUT) except FFRuntimeError as ffe: # After receiving SIGINT ffmpeg has a 255 exit code if ffe.exit_code == 255: pass else: raise ValueError("An unexpected FFRuntimeError occurred: " "{}".format(ffe)) except KeyboardInterrupt: pass # Do nothing if voluntary interruption logger.debug("ffmpeg finished")
def test_get_exe_installed(): import imageio_ffmpeg # backup any user-defined path if "IMAGEIO_FFMPEG_EXE" in os.environ: oldpath = os.environ["IMAGEIO_FFMPEG_EXE"] else: oldpath = "" # Test if download works os.environ["IMAGEIO_FFMPEG_EXE"] = "" path = imageio_ffmpeg.get_ffmpeg_exe() # cleanup os.environ.pop("IMAGEIO_FFMPEG_EXE") if oldpath: os.environ["IMAGEIO_FFMPEG_EXE"] = oldpath print(path) assert os.path.isfile(path)
def test_get_exe_installed(): import imageio_ffmpeg # backup any user-defined path if "IMAGEIO_FFMPEG_EXE" in os.environ: oldpath = os.environ["IMAGEIO_FFMPEG_EXE"] else: oldpath = "" # Test if download works os.environ["IMAGEIO_FFMPEG_EXE"] = "" path = imageio_ffmpeg.get_ffmpeg_exe() # cleanup os.environ.pop("IMAGEIO_FFMPEG_EXE") if oldpath: os.environ["IMAGEIO_FFMPEG_EXE"] = oldpath print(path) assert os.path.isfile(path)
def __run_ffmpeg(exe=im_ffm.get_ffmpeg_exe(), inputs=None, outputs=None): """ Run ffmpeg """ logger.debug("Running ffmpeg: (exe: '%s', inputs: %s, outputs: %s", exe, inputs, outputs) ffm = FFmpeg(executable=exe, inputs=inputs, outputs=outputs) try: ffm.run(stderr=subprocess.STDOUT) except FFRuntimeError as ffe: # After receiving SIGINT ffmpeg has a 255 exit code if ffe.exit_code == 255: pass else: raise ValueError("An unexpected FFRuntimeError occurred: " "{}".format(ffe)) except KeyboardInterrupt: pass # Do nothing if voluntary interruption logger.debug("ffmpeg finished")
def mux_audio(self): """ Mux audio ImageIO is a useful lib for frames > video as it also packages the ffmpeg binary however muxing audio is non-trivial, so this is done afterwards with ffmpy. A future fix could be implemented to mux audio with the frames """ logger.info("Muxing Audio...") exe = im_ffm.get_ffmpeg_exe() inputs = OrderedDict([(self.video_tmp_file, None), (self.source_video, None)]) outputs = {self.video_file: "-map 0:0 -map 1:1 -c: copy"} ffm = FFmpeg(executable=exe, global_options="-hide_banner -nostats -v 0 -y", inputs=inputs, outputs=outputs) logger.debug("Executing: %s", ffm.cmd) ffm.run() logger.debug("Removing temp file") os.remove(self.video_tmp_file)
def ffcmd(args="", console=None, task=0): ffpath = imageio_ffmpeg.get_ffmpeg_exe() cmd = ffpath + " " + args print("ffmpeg:", cmd) duration = None progress = 0 process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding="utf-8", shell=True, universal_newlines=True) for line in process.stdout: input_index = line.find("Input") # "Duration" in file name if duration is None: duration_index = line.find("Duration:") if input_index == -1 and duration_index != -1: duration_time = line.split(": ")[1].split(",")[0] duration = time2second(duration_time) else: time_index = line.find("time=") if input_index == -1 and time_index != -1: now_time = line.split("time=")[1].split(" ")[0] if duration > 0: progress = time2second(now_time) / duration print("Progress: " + str(round(progress * 100)) + "%") if console is not None: try: if task == 1: console.prepare = progress elif task == 2: console.combine = progress if not console.parent.fb.isRunning: print("terminate!!!") process.kill() break except: pass print("ffmpeg: Done!")
def test_get_exe_env(): import imageio_ffmpeg # backup any user-defined path if "IMAGEIO_FFMPEG_EXE" in os.environ: oldpath = os.environ["IMAGEIO_FFMPEG_EXE"] else: oldpath = "" # set manual path path = "invalid/path/to/my/ffmpeg" os.environ["IMAGEIO_FFMPEG_EXE"] = path try: path2 = imageio_ffmpeg.get_ffmpeg_exe() except Exception: path2 = "none" pass # cleanup os.environ.pop("IMAGEIO_FFMPEG_EXE") if oldpath: os.environ["IMAGEIO_FFMPEG_EXE"] = oldpath assert path == path2
def _set_ffmpeg_binary_path(): """Function for getting path to ffmpeg binary on your system to be used by ffmpy # Derived from ffmpeg detection code borrowed from moviepy # https://github.com/Zulko/moviepy/moviepy/config.py # The MIT License (MIT) # Copyright (c) 2015 Zulko # :raises IOError: [description] """ FFMPEG_BINARY = os.getenv("FFMPEG_BINARY", "ffmpeg-imageio") if FFMPEG_BINARY == "ffmpeg-imageio": FFMPEG_BINARY = get_ffmpeg_exe() else: success, err = _try_cmd([FFMPEG_BINARY]) if not success: raise IOError( f"{err} - The path specified for the ffmpeg binary might be wrong" ) os.environ["FFMPEG_BINARY"] = FFMPEG_BINARY
def test_get_exe_env(): import imageio_ffmpeg # backup any user-defined path if "IMAGEIO_FFMPEG_EXE" in os.environ: oldpath = os.environ["IMAGEIO_FFMPEG_EXE"] else: oldpath = "" # set manual path path = "invalid/path/to/my/ffmpeg" os.environ["IMAGEIO_FFMPEG_EXE"] = path try: path2 = imageio_ffmpeg.get_ffmpeg_exe() except Exception: path2 = "none" pass # cleanup os.environ.pop("IMAGEIO_FFMPEG_EXE") if oldpath: os.environ["IMAGEIO_FFMPEG_EXE"] = oldpath assert path == path2
def _test_for_audio_stream(self) -> bool: """ Check whether the source video file contains an audio stream. If we attempt to mux audio from a source video that does not contain an audio stream ffmpeg will crash faceswap in a fairly ugly manner. Returns ------- bool ``True`` if an audio stream is found in the source video file, otherwise ``False`` Raises ------ ValueError If a subprocess error is raised scanning the input video file """ exe = im_ffm.get_ffmpeg_exe() cmd = [exe, "-hide_banner", "-i", self._source_video, "-f", "ffmetadata", "-"] try: out = check_output(cmd, stderr=STDOUT) except CalledProcessError as err: out = err.output.decode(errors="ignore") raise ValueError("Error checking audio stream. Status: " f"{err.returncode}\n{out}") from err retval = False for line in out.splitlines(): if not line.strip().startswith(b"Stream #"): continue logger.debug("scanning Stream line: %s", line.decode(errors="ignore").strip()) if b"Audio" in line: retval = True break logger.debug("Audio found: %s", retval) return retval
def get_exe(): # pragma: no cover """ Wrapper for imageio_ffmpeg.get_ffmpeg_exe() """ import imageio_ffmpeg return imageio_ffmpeg.get_ffmpeg_exe()
import os import numpy as np import pytest import vtk import pyvista from pyvista import examples from pyvista.plotting import system_supports_plotting ffmpeg_failed = False try: try: import imageio_ffmpeg imageio_ffmpeg.get_ffmpeg_exe() except ImportError: import imageio imageio.plugins.ffmpeg.download() except: ffmpeg_failed = True TEST_DOWNLOADS = False try: if os.environ['TEST_DOWNLOADS'].lower() == 'true': TEST_DOWNLOADS = True except KeyError: pass @pytest.mark.skipif(not system_supports_plotting(), reason="Requires system to support plotting")
def count_frames_and_secs(filename, timeout=90): """ Count the number of frames and seconds in a video file. Adapted From :mod:`ffmpeg_imageio` to handle the issue of ffmpeg occasionally hanging inside a sub-process. If the operation times out then the process will try to read the data again, up to a total of 3 times. If the data still cannot be read then an exception will be raised. Note that this operation can be quite slow for large files. Parameters ---------- filename: str Full path to the video to be analyzed. timeout: str, optional The amount of time in seconds to wait for the video data before aborting. Default: ``60`` Returns ------- frames: int The number of frames in the given video file. secs: float The duration, in seconds, of the given video file. Example ------- >>> video = "/path/to/video.mp4" >>> frames, secs = count_frames_and_secs(video) """ # https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg assert isinstance(filename, str), "Video path must be a string" exe = im_ffm.get_ffmpeg_exe() iswin = sys.platform.startswith("win") logger.debug("iswin: '%s'", iswin) cmd = [ exe, "-i", filename, "-map", "0:v:0", "-c", "copy", "-f", "null", "-" ] logger.debug("FFMPEG Command: '%s'", " ".join(cmd)) attempts = 3 for attempt in range(attempts): try: logger.debug("attempt: %s of %s", attempt + 1, attempts) out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=iswin, timeout=timeout) logger.debug("Succesfully communicated with FFMPEG") break except subprocess.CalledProcessError as err: out = err.output.decode(errors="ignore") raise RuntimeError("FFMEG call failed with {}:\n{}".format( err.returncode, out)) except subprocess.TimeoutExpired as err: this_attempt = attempt + 1 if this_attempt == attempts: msg = ( "FFMPEG hung while attempting to obtain the frame count. " "Sometimes this issue resolves itself, so you can try running again. " "Otherwise use the Effmpeg Tool to extract the frames from your video into " "a folder, and then run the requested Faceswap process on that folder." ) raise FaceswapError(msg) from err logger.warning( "FFMPEG hung while attempting to obtain the frame count. " "Retrying %s of %s", this_attempt + 1, attempts) continue # Note that other than with the sub-process calls below, ffmpeg wont hang here. # Worst case Python will stop/crash and ffmpeg will continue running until done. nframes = nsecs = None for line in reversed(out.splitlines()): if not line.startswith(b"frame="): continue line = line.decode(errors="ignore") logger.debug("frame line: '%s'", line) idx = line.find("frame=") if idx >= 0: splitframes = line[idx:].split("=", 1)[-1].lstrip().split(" ", 1)[0].strip() nframes = int(splitframes) idx = line.find("time=") if idx >= 0: splittime = line[idx:].split("=", 1)[-1].lstrip().split(" ", 1)[0].strip() nsecs = convert_to_secs(*splittime.split(":")) logger.debug("nframes: %s, nsecs: %s", nframes, nsecs) return nframes, nsecs raise RuntimeError("Could not get number of frames")
def count_frames(filename, fast=False): """ Count the number of frames in a video file There is no guaranteed accurate way to get a count of video frames without iterating through a video and decoding every frame. :func:`count_frames` can return an accurate count (albeit fairly slowly) or a possibly less accurate count, depending on the :attr:`fast` parameter. A progress bar is displayed. Parameters ---------- filename: str Full path to the video to return the frame count from. fast: bool, optional Whether to count the frames without decoding them. This is significantly faster but accuracy is not guaranteed. Default: ``False``. Returns ------- int: The number of frames in the given video file. Example ------- >>> filename = "/path/to/video.mp4" >>> frame_count = count_frames(filename) """ logger.debug("filename: %s, fast: %s", filename, fast) assert isinstance(filename, str), "Video path must be a string" cmd = [im_ffm.get_ffmpeg_exe(), "-i", filename, "-map", "0:v:0"] if fast: cmd.extend(["-c", "copy"]) cmd.extend(["-f", "null", "-"]) logger.debug("FFMPEG Command: '%s'", " ".join(cmd)) process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True) pbar = None duration = None init_tqdm = False update = 0 frames = 0 while True: output = process.stdout.readline().strip() if output == "" and process.poll() is not None: break if output.startswith("Duration:"): logger.debug("Duration line: %s", output) idx = output.find("Duration:") + len("Duration:") duration = int(convert_to_secs(*output[idx:].split(",", 1)[0].strip().split(":"))) logger.debug("duration: %s", duration) if output.startswith("frame="): logger.debug("frame line: %s", output) if not init_tqdm: logger.debug("Initializing tqdm") pbar = tqdm(desc="Counting Video Frames", leave=False, total=duration, unit="secs") init_tqdm = True time_idx = output.find("time=") + len("time=") frame_idx = output.find("frame=") + len("frame=") frames = int(output[frame_idx:].strip().split(" ")[0].strip()) vid_time = int(convert_to_secs(*output[time_idx:].split(" ")[0].strip().split(":"))) logger.debug("frames: %s, vid_time: %s", frames, vid_time) prev_update = update update = vid_time pbar.update(update - prev_update) if pbar is not None: pbar.close() return_code = process.poll() logger.debug("Return code: %s, frames: %s", return_code, frames) return frames
def exe_path(): """Returns path to ffmpeg executable.""" return imageio_ffmpeg.get_ffmpeg_exe()
def __init__(self): self.color_dic = { "Rainbow 4x": "color4x", "Rainbow 2x": "color2x", "Rainbow 1x": "color1x", "White": "white", "Red": "red", "Green": "green", "Blue": "blue", "Yellow": "yellow", "Magenta": "magenta", "Purple": "purple", "Cyan": "cyan", "Light Green": "lightgreen", "Gradient: Green - Blue": "green-blue", "Gradient: Magenta - Purple": "magenta-purple", "Gradient: Red - Yellow": "red-yellow", "Gradient: Yellow - Green": "yellow-green", "Gradient: Blue - Purple": "blue-purple", } self.image_path = None self.sound_path = None self.logo_path = None self.output_path = None self.text_bottom = "" self.font = "./Source/font.otf" self.font_alpha = 0.85 self.frame_width = 540 self.frame_height = 540 self.fps = 30 self.bit_rate = 0.6 # in Mb/s self.audio_bit_rate = 320 # in kb/s self.audio_normal = False self.spectrum_color = "color4x" self.bins = 80 self.fq_low = 20 self.fq_up = 1500 self.scalar = 1.0 self._debug_bg = False self._temp_audio_path = r"./Temp/temp.wav" self._temp_video_path = r"./Temp/temp.mp4" self.ensure_dir(self._temp_audio_path) self._ffmpeg_path = imageio_ffmpeg.get_ffmpeg_exe() self._frame_size = min(self.frame_width, self.frame_height) self._font_size = int( round(30 / 1080 * self._frame_size)) # A good ratio for text and frame size self._blur = int(round(2 / 1080 * self._frame_size)) self._blur_bg = int(round(41 / 1080 * self._frame_size)) self._line_thick = int(round(4 / 1080 * self._frame_size)) self._amplify = self.scalar * 7 / 80 * self.bins * np.power( 1500 / (self.fq_up - self.fq_low), 0.5) self.visualizer = None self.analyzer = None self.writer = None self.total_frames = None self.frame_buffer = [] self.frame_pt = 0 self.encoder_pt = 0 self.isRunning = False self._console = None
class Effmpeg(): """ Class that allows for "easy" ffmpeg use. It provides a nice cli interface for common video operations. """ _actions_req_fps = ["extract", "gen_vid"] _actions_req_ref_video = ["mux_audio"] _actions_can_preview = [ "gen_vid", "mux_audio", "rescale", "rotate", "slice" ] _actions_can_use_ref_video = ["gen_vid"] _actions_have_dir_output = ["extract"] _actions_have_vid_output = [ "gen_vid", "mux_audio", "rescale", "rotate", "slice" ] _actions_have_print_output = ["get_fps", "get_info"] _actions_have_dir_input = ["gen_vid"] _actions_have_vid_input = [ "extract", "get_fps", "get_info", "rescale", "rotate", "slice" ] # Class variable that stores the target executable (ffmpeg or ffplay) _executable = im_ffm.get_ffmpeg_exe() # Class variable that stores the common ffmpeg arguments based on verbosity __common_ffmpeg_args_dict = { "normal": "-hide_banner ", "quiet": "-loglevel panic -hide_banner ", "verbose": '' } # _common_ffmpeg_args is the class variable that will get used by various # actions and it will be set by the process_arguments() method based on # passed verbosity _common_ffmpeg_args = '' def __init__(self, arguments): logger.debug("Initializing %s: (arguments: %s)", self.__class__.__name__, arguments) self.args = arguments self.exe = im_ffm.get_ffmpeg_exe() self.input = DataItem() self.output = DataItem() self.ref_vid = DataItem() self.start = "" self.end = "" self.duration = "" self.print_ = False logger.debug("Initialized %s", self.__class__.__name__) def process(self): """ EFFMPEG Process """ logger.debug("Running Effmpeg") # Format action to match the method name self.args.action = self.args.action.replace('-', '_') logger.debug("action: '%s", self.args.action) # Instantiate input DataItem object self.input = DataItem(path=self.args.input) # Instantiate output DataItem object if self.args.action in self._actions_have_dir_output: self.output = DataItem(path=self.__get_default_output()) elif self.args.action in self._actions_have_vid_output: if self.__check_have_fps(self.args.fps) > 0: self.output = DataItem(path=self.__get_default_output(), fps=self.args.fps) else: self.output = DataItem(path=self.__get_default_output()) if self.args.ref_vid is None \ or self.args.ref_vid == '': self.args.ref_vid = None # Instantiate ref_vid DataItem object self.ref_vid = DataItem(path=self.args.ref_vid) # Check that correct input and output arguments were provided if self.args.action in self._actions_have_dir_input and not self.input.is_type( "dir"): raise ValueError("The chosen action requires a directory as its " "input, but you entered: " "{}".format(self.input.path)) if self.args.action in self._actions_have_vid_input and not self.input.is_type( "vid"): raise ValueError("The chosen action requires a video as its " "input, but you entered: " "{}".format(self.input.path)) if self.args.action in self._actions_have_dir_output and not self.output.is_type( "dir"): raise ValueError("The chosen action requires a directory as its " "output, but you entered: " "{}".format(self.output.path)) if self.args.action in self._actions_have_vid_output and not self.output.is_type( "vid"): raise ValueError("The chosen action requires a video as its " "output, but you entered: " "{}".format(self.output.path)) # Check that ref_vid is a video when it needs to be if self.args.action in self._actions_req_ref_video: if self.ref_vid.is_type("none"): raise ValueError("The file chosen as the reference video is " "not a video, either leave the field blank " "or type 'None': " "{}".format(self.ref_vid.path)) elif self.args.action in self._actions_can_use_ref_video: if self.ref_vid.is_type("none"): logger.warning( "Warning: no reference video was supplied, even though " "one may be used with the chosen action. If this is " "intentional then ignore this warning.") # Process start and duration arguments self.start = self.parse_time(self.args.start) self.end = self.parse_time(self.args.end) if not self.__check_equals_time(self.args.end, "00:00:00"): self.duration = self.__get_duration(self.start, self.end) else: self.duration = self.parse_time(str(self.args.duration)) # If fps was left blank in gui, set it to default -1.0 value if self.args.fps == '': self.args.fps = str(-1.0) # Try to set fps automatically if needed and not supplied by user if self.args.action in self._actions_req_fps \ and self.__convert_fps(self.args.fps) <= 0: if self.__check_have_fps(['r', 'i']): _error_str = "No fps, input or reference video was supplied, " _error_str += "hence it's not possible to " _error_str += "'{}'.".format(self.args.action) raise ValueError(_error_str) if self.output.fps is not None and self.__check_have_fps( ['r', 'i']): self.args.fps = self.output.fps elif self.ref_vid.fps is not None and self.__check_have_fps(['i']): self.args.fps = self.ref_vid.fps elif self.input.fps is not None and self.__check_have_fps(['r']): self.args.fps = self.input.fps # Processing transpose if self.args.transpose is None or \ self.args.transpose.lower() == "none": self.args.transpose = None else: self.args.transpose = self.args.transpose[1] # Processing degrees if self.args.degrees is None \ or self.args.degrees.lower() == "none" \ or self.args.degrees == '': self.args.degrees = None elif self.args.transpose is None: try: int(self.args.degrees) except ValueError: logger.error( "You have entered an invalid value for degrees: %s", self.args.degrees) exit(1) # Set executable based on whether previewing or not if self.args.preview and self.args.action in self._actions_can_preview: self.exe = 'ffplay' self.output = DataItem() # Set verbosity of output self.__set_verbosity(self.args.quiet, self.args.verbose) # Set self.print_ to True if output needs to be printed to stdout if self.args.action in self._actions_have_print_output: self.print_ = True self.effmpeg_process() logger.debug("Finished Effmpeg process") def effmpeg_process(self): """ The effmpeg process """ kwargs = { "input_": self.input, "output": self.output, "ref_vid": self.ref_vid, "fps": self.args.fps, "extract_ext": self.args.extract_ext, "start": self.start, "duration": self.duration, "mux_audio": self.args.mux_audio, "degrees": self.args.degrees, "transpose": self.args.transpose, "scale": self.args.scale, "print_": self.print_, "preview": self.args.preview, "exe": self.exe } action = getattr(self, self.args.action) action(**kwargs) @staticmethod def extract( input_=None, output=None, fps=None, # pylint:disable=unused-argument extract_ext=None, start=None, duration=None, **kwargs): """ Extract video to image frames """ logger.debug( "input_: %s, output: %s, fps: %s, extract_ext: '%s', start: %s, duration: %s", input_, output, fps, extract_ext, start, duration) _input_opts = Effmpeg._common_ffmpeg_args[:] if start is not None and duration is not None: _input_opts += '-ss {} -t {}'.format(start, duration) _input = {input_.path: _input_opts} _output_opts = '-y -vf fps="' + str(fps) + '" -q:v 1' _output_path = output.path + "/" + input_.name + "_%05d" + extract_ext _output = {_output_path: _output_opts} os.makedirs(output.path, exist_ok=True) logger.debug("_input: %s, _output: %s", _input, _output) Effmpeg.__run_ffmpeg(inputs=_input, outputs=_output) @staticmethod def gen_vid( input_=None, output=None, fps=None, # pylint:disable=unused-argument mux_audio=False, ref_vid=None, preview=False, exe=None, **kwargs): """ Generate Video """ logger.debug( "input: %s, output: %s, fps: %s, mux_audio: %s, ref_vid: '%s', preview: %s, " "exe: '%s'", input, output, fps, mux_audio, ref_vid, preview, exe) filename = Effmpeg.__get_extracted_filename(input_.path) _input_opts = Effmpeg._common_ffmpeg_args[:] _input_path = os.path.join(input_.path, filename) _fps_arg = '-r ' + str(fps) + ' ' _input_opts += _fps_arg + "-f image2 " _output_opts = _fps_arg if not preview: _output_opts = '-y ' + _output_opts + ' -c:v libx264' if mux_audio: _ref_vid_opts = '-c copy -map 0:0 -map 1:1' if preview: raise ValueError("Preview for gen-vid with audio muxing is " "not supported.") _output_opts = _ref_vid_opts + ' ' + _output_opts _inputs = OrderedDict([(_input_path, _input_opts), (ref_vid.path, None)]) else: _inputs = {_input_path: _input_opts} _outputs = {output.path: _output_opts} logger.debug("_inputs: %s, _outputs: %s", _inputs, _outputs) Effmpeg.__run_ffmpeg(exe=exe, inputs=_inputs, outputs=_outputs) @staticmethod def get_fps(input_=None, print_=False, **kwargs): """ Get Frames per Second """ logger.debug("input_: %s, print_: %s, kwargs: %s", input_, print_, kwargs) input_ = input_ if isinstance(input_, str) else input_.path logger.debug("input: %s", input_) reader = imageio.get_reader(input_) _fps = reader.get_meta_data()["fps"] logger.debug(_fps) reader.close() if print_: logger.info("Video fps: %s", _fps) return _fps @staticmethod def get_info(input_=None, print_=False, **kwargs): """ Get video Info """ logger.debug("input_: %s, print_: %s, kwargs: %s", input_, print_, kwargs) input_ = input_ if isinstance(input_, str) else input_.path logger.debug("input: %s", input_) reader = imageio.get_reader(input_) out = reader.get_meta_data() logger.debug(out) reader.close() if print_: logger.info("======== Video Info ========", ) logger.info("path: %s", input_) for key, val in out.items(): logger.info("%s: %s", key, val) return out @staticmethod def rescale( input_=None, output=None, scale=None, # pylint:disable=unused-argument preview=False, exe=None, **kwargs): """ Rescale Video """ _input_opts = Effmpeg._common_ffmpeg_args[:] _output_opts = '-vf scale="' + str(scale) + '"' if not preview: _output_opts = '-y ' + _output_opts _inputs = {input_.path: _input_opts} _outputs = {output.path: _output_opts} Effmpeg.__run_ffmpeg(exe=exe, inputs=_inputs, outputs=_outputs) @staticmethod def rotate( input_=None, output=None, degrees=None, # pylint:disable=unused-argument transpose=None, preview=None, exe=None, **kwargs): """ Rotate Video """ if transpose is None and degrees is None: raise ValueError("You have not supplied a valid transpose or " "degrees value:\ntranspose: {}\ndegrees: " "{}".format(transpose, degrees)) _input_opts = Effmpeg._common_ffmpeg_args[:] _output_opts = '-vf ' if not preview: _output_opts = '-y -c:a copy ' + _output_opts _bilinear = '' if transpose is not None: _output_opts += 'transpose="' + str(transpose) + '"' elif int(degrees) != 0: if int(degrees) % 90 == 0 and int(degrees) != 0: _bilinear = ":bilinear=0" _output_opts += 'rotate="' + str(degrees) + '*(PI/180)' _output_opts += _bilinear + '" ' _inputs = {input_.path: _input_opts} _outputs = {output.path: _output_opts} Effmpeg.__run_ffmpeg(exe=exe, inputs=_inputs, outputs=_outputs) @staticmethod def mux_audio( input_=None, output=None, ref_vid=None, # pylint:disable=unused-argument preview=None, exe=None, **kwargs): """ Mux Audio """ _input_opts = Effmpeg._common_ffmpeg_args[:] _ref_vid_opts = None _output_opts = '-y -c copy -map 0:0 -map 1:1 -shortest' if preview: raise ValueError("Preview with audio muxing is not supported.") # if not preview: # _output_opts = '-y ' + _output_opts _inputs = OrderedDict([(input_.path, _input_opts), (ref_vid.path, _ref_vid_opts)]) _outputs = {output.path: _output_opts} Effmpeg.__run_ffmpeg(exe=exe, inputs=_inputs, outputs=_outputs) @staticmethod def slice( input_=None, output=None, start=None, # pylint:disable=unused-argument duration=None, preview=None, exe=None, **kwargs): """ Slice Video """ _input_opts = Effmpeg._common_ffmpeg_args[:] _input_opts += "-ss " + start _output_opts = "-t " + duration + " " if not preview: _output_opts = '-y ' + _output_opts + "-vcodec copy -acodec copy" _inputs = {input_.path: _input_opts} _output = {output.path: _output_opts} Effmpeg.__run_ffmpeg(exe=exe, inputs=_inputs, outputs=_output) # Various helper methods @classmethod def __set_verbosity(cls, quiet, verbose): if verbose: cls._common_ffmpeg_args = cls.__common_ffmpeg_args_dict["verbose"] elif quiet: cls._common_ffmpeg_args = cls.__common_ffmpeg_args_dict["quiet"] else: cls._common_ffmpeg_args = cls.__common_ffmpeg_args_dict["normal"] def __get_default_output(self): """ Set output to the same directory as input if the user didn't specify it. """ if self.args.output == "": if self.args.action in self._actions_have_dir_output: retval = os.path.join(self.input.dirname, 'out') elif self.args.action in self._actions_have_vid_output: if self.input.is_type("media"): # Using the same extension as input leads to very poor # output quality, hence the default is mkv for now retval = os.path.join(self.input.dirname, "out.mkv") # + self.input.ext) else: # case if input was a directory retval = os.path.join(self.input.dirname, 'out.mkv') else: retval = self.args.output logger.debug(retval) return retval def __check_have_fps(self, items): items_to_check = list() for i in items: if i == 'r': items_to_check.append('ref_vid') elif i == 'i': items_to_check.append('input') elif i == 'o': items_to_check.append('output') return all(getattr(self, i).fps is None for i in items_to_check) @staticmethod def __run_ffmpeg(exe=im_ffm.get_ffmpeg_exe(), inputs=None, outputs=None): """ Run ffmpeg """ logger.debug("Running ffmpeg: (exe: '%s', inputs: %s, outputs: %s", exe, inputs, outputs) ffm = FFmpeg(executable=exe, inputs=inputs, outputs=outputs) try: ffm.run(stderr=subprocess.STDOUT) except FFRuntimeError as ffe: # After receiving SIGINT ffmpeg has a 255 exit code if ffe.exit_code == 255: pass else: raise ValueError("An unexpected FFRuntimeError occurred: " "{}".format(ffe)) except KeyboardInterrupt: pass # Do nothing if voluntary interruption logger.debug("ffmpeg finished") @staticmethod def __convert_fps(fps): """ Convert to Frames per Second """ if '/' in fps: _fps = fps.split('/') retval = float(_fps[0]) / float(_fps[1]) else: retval = float(fps) logger.debug(retval) return retval @staticmethod def __get_duration(start_time, end_time): """ Get the duration """ start = [int(i) for i in start_time.split(':')] end = [int(i) for i in end_time.split(':')] start = datetime.timedelta(hours=start[0], minutes=start[1], seconds=start[2]) end = datetime.timedelta(hours=end[0], minutes=end[1], seconds=end[2]) delta = end - start secs = delta.total_seconds() retval = '{:02}:{:02}:{:02}'.format(int(secs // 3600), int(secs % 3600 // 60), int(secs % 60)) logger.debug(retval) return retval @staticmethod def __get_extracted_filename(path): """ Get the extracted filename """ logger.debug("path: '%s'", path) filename = '' for file in os.listdir(path): if any(i in file for i in DataItem.img_ext): filename = file break logger.debug("sample filename: '%s'", filename) filename, img_ext = os.path.splitext(filename) zero_pad = Effmpeg.__get_zero_pad(filename) name = filename[:-zero_pad] retval = "{}%{}d{}".format(name, zero_pad, img_ext) logger.debug("filename: %s, img_ext: '%s', zero_pad: %s, name: '%s'", filename, img_ext, zero_pad, name) logger.debug(retval) return retval @staticmethod def __get_zero_pad(filename): """ Return the starting position of zero padding from a filename """ chkstring = filename[::-1] logger.trace("filename: %s, chkstring: %s", filename, chkstring) pos = 0 for pos in range(len(chkstring)): if not chkstring[pos].isdigit(): break logger.debug("filename: '%s', pos: %s", filename, pos) return pos @staticmethod def __check_is_valid_time(value): """ Check valid time """ val = value.replace(':', '') retval = val.isdigit() logger.debug("value: '%s', retval: %s", value, retval) return retval @staticmethod def __check_equals_time(value, time): """ Check equals time """ val = value.replace(':', '') tme = time.replace(':', '') retval = val.zfill(6) == tme.zfill(6) logger.debug("value: '%s', time: %s, retval: %s", value, time, retval) return retval @staticmethod def parse_time(txt): """ Parse Time """ clean_txt = txt.replace(':', '') hours = clean_txt[0:2] minutes = clean_txt[2:4] seconds = clean_txt[4:6] retval = hours + ':' + minutes + ':' + seconds logger.debug("txt: '%s', retval: %s", txt, retval) return retval
def get_exe(): # pragma: no cover """ Wrapper for imageio_ffmpeg.get_ffmpeg_exe() """ import imageio_ffmpeg return imageio_ffmpeg.get_ffmpeg_exe()
def get_frame_info(self, frame_pts=None, keyframes=None): """ Store the source video's keyframes in :attr:`_frame_info" for the current video for use in :func:`initialize`. Parameters ---------- frame_pts: list, optional A list corresponding to the video frame count of the pts_time per frame. If this and `keyframes` are provided, then analyzing the video is skipped and the values from the given lists are used. Default: ``None`` keyframes: list, optional A list containing the frame numbers of each key frame. if this and `frame_pts` are provided, then analyzing the video is skipped and the values from the given lists are used. Default: ``None`` """ if frame_pts is not None and keyframes is not None: logger.debug( "Video meta information provided. Not analyzing video") self._frame_pts = frame_pts self._keyframes = keyframes return len(frame_pts), dict(pts_time=self._frame_pts, keyframes=self._keyframes) assert isinstance(self._filename, str), "Video path must be a string" cmd = [ im_ffm.get_ffmpeg_exe(), "-hide_banner", "-copyts", "-i", self._filename, "-vf", "showinfo", "-start_number", "0", "-an", "-f", "null", "-" ] logger.debug("FFMPEG Command: '%s'", " ".join(cmd)) process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True) frame_pts = [] key_frames = [] last_update = 0 pbar = tqdm(desc="Analyzing Video", leave=False, total=int(self._meta["duration"]), unit="secs") while True: output = process.stdout.readline().strip() if output == "" and process.poll() is not None: break if "iskey" not in output: continue logger.trace("Keyframe line: %s", output) line = re.split(r"\s+|:\s*", output) pts_time = float(line[line.index("pts_time") + 1]) frame_no = int(line[line.index("n") + 1]) frame_pts.append(pts_time) if "iskey:1" in output: key_frames.append(frame_no) logger.trace("pts_time: %s, frame_no: %s", pts_time, frame_no) if int(pts_time) == last_update: # Floating points make TQDM display poorly, so only update on full # second increments continue pbar.update(int(pts_time) - last_update) last_update = int(pts_time) pbar.close() return_code = process.poll() frame_count = len(frame_pts) logger.debug( "Return code: %s, frame_pts: %s, keyframes: %s, frame_count: %s", return_code, frame_pts, key_frames, frame_count) self._frame_pts = frame_pts self._keyframes = key_frames return frame_count, dict(pts_time=self._frame_pts, keyframes=self._keyframes)
def count_frames_and_secs(path, timeout=60): """ Adapted From ffmpeg_imageio, to handle occasional hanging issue: https://github.com/imageio/imageio-ffmpeg Get the number of frames and number of seconds for the given video file. Note that this operation can be quite slow for large files. Disclaimer: I've seen this produce different results from actually reading the frames with older versions of ffmpeg (2.x). Therefore I cannot say with 100% certainty that the returned values are always exact. """ # https://stackoverflow.com/questions/2017843/fetch-frame-count-with-ffmpeg logger = logging.getLogger(__name__) # pylint:disable=invalid-name assert isinstance(path, str), "Video path must be a string" exe = im_ffm.get_ffmpeg_exe() iswin = sys.platform.startswith("win") logger.debug("iswin: '%s'", iswin) cmd = [exe, "-i", path, "-map", "0:v:0", "-c", "copy", "-f", "null", "-"] logger.debug("FFMPEG Command: '%s'", " ".join(cmd)) attempts = 3 for attempt in range(attempts): try: logger.debug("attempt: %s of %s", attempt + 1, attempts) out = subprocess.check_output(cmd, stderr=subprocess.STDOUT, shell=iswin, timeout=timeout) logger.debug("Succesfully communicated with FFMPEG") break except subprocess.CalledProcessError as err: out = err.output.decode(errors="ignore") raise RuntimeError("FFMEG call failed with {}:\n{}".format( err.returncode, out)) except subprocess.TimeoutExpired as err: this_attempt = attempt + 1 if this_attempt == attempts: msg = ( "FFMPEG hung while attempting to obtain the frame count. " "Sometimes this issue resolves itself, so you can try running again. " "Otherwise use the Effmpeg Tool to extract the frames from your video into " "a folder, and then run the requested Faceswap process on that folder." ) raise FaceswapError(msg) from err logger.warning( "FFMPEG hung while attempting to obtain the frame count. " "Retrying %s of %s", this_attempt + 1, attempts) continue # Note that other than with the subprocess calls below, ffmpeg wont hang here. # Worst case Python will stop/crash and ffmpeg will continue running until done. nframes = nsecs = None for line in reversed(out.splitlines()): if not line.startswith(b"frame="): continue line = line.decode(errors="ignore") logger.debug("frame line: '%s'", line) idx = line.find("frame=") if idx >= 0: splitframes = line[idx:].split("=", 1)[-1].lstrip().split(" ", 1)[0].strip() nframes = int(splitframes) idx = line.find("time=") if idx >= 0: splittime = line[idx:].split("=", 1)[-1].lstrip().split(" ", 1)[0].strip() nsecs = convert_to_secs(*splittime.split(":")) logger.debug("nframes: %s, nsecs: %s", nframes, nsecs) return nframes, nsecs raise RuntimeError("Could not get number of frames") # pragma: no cover
def generate(): N = get_video_length(video) # video keypoints driving_keypoints = [] for i, kp in enumerate(generate_face_keypoints(video)): driving_keypoints.append(kp) yield data( { "type": "update", "field": "extract_keypoints", "iteration": i, "total": N + 1, } ) # image keypoints source_image = resize(imageio.imread(f"static/image/{image}"), (256, 256)) [source_keypoints] = fa.get_landmarks(source_image * 255) yield data( { "type": "update", "field": "extract_keypoints", "iteration": i + 1, "total": N + 1, } ) # alignment optimization errors = [] transforms = [] for i, (trans, err) in enumerate( generate_alignment_candidates(source_keypoints, driving_keypoints) ): errors.append(err) transforms.append(trans) yield data( { "type": "update", "field": "compute_alignment", "iteration": i, "total": N, } ) best_frame = np.argmin(errors) best_transform = transforms[best_frame] # warp video to align with image warped = [] for i, img in enumerate(generate_warped_video(video, best_transform)): warped.append(img) yield data({"type": "update", "field": "warp", "iteration": i, "total": N}) # morph morphed_forward = [] for i, out in enumerate( generate_morphed_video(source_image, warped[best_frame:]) ): morphed_forward.append(out) yield data( {"type": "update", "field": "morphed", "iteration": i, "total": N} ) morphed_backward = [] for j, out in enumerate( generate_morphed_video(source_image, warped[: (best_frame + 1)][::-1]) ): morphed_backward.append(out) yield data( {"type": "update", "field": "morphed", "iteration": i + j, "total": N} ) morphed = morphed_backward[::-1] + morphed_forward[1:] # figure out input video fps ffmpeg = get_ffmpeg_exe() p = subprocess.Popen( f"{ffmpeg} -i static/video/{video}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) _, info = p.communicate() match = regex.search("([0-9\\.]+) fps", info.decode("utf8")) rate = float(match.groups()[0]) # Video 1/3: write out soundless video soundless_video = f"static/video/soundless_{video}" imageio.mimwrite( soundless_video, [(x * 255).astype(np.uint8) for x in morphed], fps=rate, quality=10, ) yield data({"type": "update", "field": "result", "iteration": 0, "total": 3}) # Video 2/3: extract sound from original video base_video_name, _ = os.path.splitext(video) video_full = f"static/video/{video}" audio_full = f"static/video/audio_{base_video_name}.aac" os.system(f"{ffmpeg} -y -i {video_full} -vn -acodec copy {audio_full}") yield data({"type": "update", "field": "result", "iteration": 1, "total": 3}) # Video 3/3: extract sound from original video audio_scale = 0.8 result_video = f"static/video/result_{video}" os.system( f"{ffmpeg} -y -i {soundless_video} -i {audio_full} -af asetrate=48000*{audio_scale},atempo={1/audio_scale} -c:v copy -c:a aac {result_video}" ) yield data({"type": "update", "field": "result", "iteration": 2, "total": 3}) # all done, download video yield data({"type": "download", "url": result_video})
def __init__(self): self.color_dic = { "Rainbow 4x": "color4x", "Rainbow 2x": "color2x", "Rainbow 1x": "color1x", "White": "white", "Black": "black", "Gray": "gray", "Red": "red", "Green": "green", "Blue": "blue", "Yellow": "yellow", "Magenta": "magenta", "Purple": "purple", "Cyan": "cyan", "Light Green": "lightgreen", "Gradient: Green - Blue": "green-blue", "Gradient: Magenta - Purple": "magenta-purple", "Gradient: Red - Yellow": "red-yellow", "Gradient: Yellow - Green": "yellow-green", "Gradient: Blue - Purple": "blue-purple", } self.image_path = None self.bg_path = None self.sound_path = None self.logo_path = None self.output_path = None self.bg_img = None self.logofile = None self.text_bottom = "" self.font = getPath("Source/font.otf") self.frame_width = 540 self.frame_height = 540 self.fps = 30 self.bit_rate = 0.6 # in Mb/s self.audio_bit_rate = 320 # in kb/s self.audio_normal = False self.spectrum_color = "color4x" self._bright = 1.0 self._saturation = 1.0 self.bins = 80 self.smooth = 0 self.fq_low = 20 self.fq_up = 1500 self.scalar = 1.0 self._debug_bg = False self._temp_audio_path = getPath("Temp/temp.wav") self._temp_video_path = getPath("Temp/temp.mp4") self.ensure_dir(self._temp_audio_path) try: self._ffmpeg_path = imageio_ffmpeg.get_ffmpeg_exe() except: self._ffmpeg_path = None self._frame_size = 0 self._relsize = 1.0 self._font_size = 0 self._text_color = (0, 0, 0, 0) self._text_glow = False self._yoffset = 0 # A good ratio for text and frame size self._blur = 0 self._blur_bg = 0 self.blur_bg = True self.use_glow = False self.style = 0 self.linewidth = 1.0 self.rotate = 0 self._line_thick = 0 self._amplify = self.setAmplify() self.visualizer = None self.analyzer = None self.fg_img = None self.writer = None self.total_frames = None self.frame_buffer = [] self.frame_pt = 0 self.encoder_pt = 0 self.isRunning = False self._console = None self.frame_lock = None self.bg_mode = 0 self.bg_blended = False self.ffmpegCheck()
def count_frames(filename): """ Count the number of frames in a video file Unfortunately there is no guaranteed accurate way to get a count of video frames without iterating through the video. This counts the frames, displaying a progress bar to keep the user abreast of progress Parameters ---------- filename: str Full path to the video to return the frame count from. Returns ------- int: The number of frames in the given video file. """ assert isinstance(filename, str), "Video path must be a string" cmd = [ im_ffm.get_ffmpeg_exe(), "-i", filename, "-map", "0:v:0", "-f", "null", "-" ] logger.debug("FFMPEG Command: '%s'", " ".join(cmd)) process = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, universal_newlines=True) pbar = None duration = None init_tqdm = False update = 0 frames = 0 while True: output = process.stdout.readline().strip() if output == "" and process.poll() is not None: break if output.startswith("Duration:"): logger.debug("Duration line: %s", output) idx = output.find("Duration:") + len("Duration:") duration = int( convert_to_secs( *output[idx:].split(",", 1)[0].strip().split(":"))) logger.debug("duration: %s", duration) if output.startswith("frame="): logger.debug("frame line: %s", output) if not init_tqdm: logger.debug("Initializing tqdm") pbar = tqdm(desc="Counting Video Frames", total=duration, unit="secs") init_tqdm = True time_idx = output.find("time=") + len("time=") frame_idx = output.find("frame=") + len("frame=") frames = int(output[frame_idx:].strip().split(" ")[0].strip()) vid_time = int( convert_to_secs( *output[time_idx:].split(" ")[0].strip().split(":"))) logger.debug("frames: %s, vid_time: %s", frames, vid_time) prev_update = update update = vid_time pbar.update(update - prev_update) if pbar is not None: pbar.close() return_code = process.poll() logger.debug("Return code: %s, frames: %s", return_code, frames) return frames