Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
    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
Ejemplo n.º 4
0
    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()
Ejemplo n.º 5
0
Archivo: ffmpeg.py Proyecto: procule/df
 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)
Ejemplo n.º 6
0
 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()
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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!")
Ejemplo n.º 9
0
 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__)
Ejemplo n.º 10
0
 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__)
Ejemplo n.º 11
0
 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())
Ejemplo n.º 12
0
    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()
Ejemplo n.º 13
0
 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")
Ejemplo n.º 14
0
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)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
0
 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")
Ejemplo n.º 17
0
 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)
Ejemplo n.º 18
0
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!")
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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
Ejemplo n.º 21
0
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
Ejemplo n.º 22
0
    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
Ejemplo n.º 23
0
def get_exe():  # pragma: no cover
    """ Wrapper for imageio_ffmpeg.get_ffmpeg_exe()
    """
    import imageio_ffmpeg

    return imageio_ffmpeg.get_ffmpeg_exe()
Ejemplo n.º 24
0
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")
Ejemplo n.º 25
0
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")
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
def exe_path():
    """Returns path to ffmpeg executable."""
    return imageio_ffmpeg.get_ffmpeg_exe()
Ejemplo n.º 28
0
    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
Ejemplo n.º 29
0
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
Ejemplo n.º 30
0
def get_exe():  # pragma: no cover
    """ Wrapper for imageio_ffmpeg.get_ffmpeg_exe()
    """
    import imageio_ffmpeg

    return imageio_ffmpeg.get_ffmpeg_exe()
Ejemplo n.º 31
0
    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)
Ejemplo n.º 32
0
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
Ejemplo n.º 33
0
    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})
Ejemplo n.º 34
0
    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()
Ejemplo n.º 35
0
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