コード例 #1
0
    def list(arg):
        """Returns a list of all valid entries for the ``font`` or ``color`` argument of
        ``TextClip``.
        """
        popen_params = cross_platform_popen_params({
            "stdout": sp.PIPE,
            "stderr": sp.DEVNULL,
            "stdin": sp.DEVNULL
        })

        process = sp.Popen([IMAGEMAGICK_BINARY, "-list", arg],
                           encoding="utf-8",
                           **popen_params)
        result = process.communicate()[0]
        lines = result.splitlines()

        if arg == "font":
            # Slice removes first 8 characters: "  Font: "
            return [line[8:] for line in lines if line.startswith("  Font:")]
        elif arg == "color":
            # Each line is of the format "aqua  srgb(0,255,255)  SVG" so split
            # on space and take the first item to get the color name.
            # The first 5 lines are header information, not colors, so ignore
            return [line.split(" ")[0] for line in lines[5:]]
        else:
            raise Exception(
                "Moviepy Error: Argument must equal 'font' or 'color'")
コード例 #2
0
    def initialize(self, start_time=0):
        """
        Opens the file, creates the pipe.
        Sets self.pos to the appropriate value (1 if start_time == 0 because
        it pre-reads the first frame)
        """

        self.close(delete_lastread=False)  # if any

        if start_time != 0:
            offset = min(1, start_time)
            i_arg = [
                "-ss",
                "%.06f" % (start_time - offset),
                "-i",
                self.filename,
                "-ss",
                "%.06f" % offset,
            ]
        else:
            i_arg = ["-i", self.filename]

        cmd = (
            [FFMPEG_BINARY]
            + i_arg
            + [
                "-loglevel",
                "error",
                "-f",
                "image2pipe",
                "-vf",
                "scale=%d:%d" % tuple(self.size),
                "-sws_flags",
                self.resize_algo,
                "-pix_fmt",
                self.pixel_format,
                "-vcodec",
                "rawvideo",
                "-",
            ]
        )
        popen_params = cross_platform_popen_params(
            {
                "bufsize": self.bufsize,
                "stdout": sp.PIPE,
                "stderr": sp.PIPE,
                "stdin": sp.DEVNULL,
            }
        )
        self.proc = sp.Popen(cmd, **popen_params)

        # self.pos represents the (0-indexed) index of the frame that is next in line
        # to be read by self.read_frame().
        # Eg when self.pos is 1, the 2nd frame will be read next.
        self.pos = self.get_frame_number(start_time)
        self.lastread = self.read_frame()
コード例 #3
0
ファイル: ffmpeg_audiowriter.py プロジェクト: ziseon/moviepy
    def __init__(
        self,
        filename,
        fps_input,
        nbytes=2,
        nchannels=2,
        codec="libfdk_aac",
        bitrate=None,
        input_video=None,
        logfile=None,
        ffmpeg_params=None,
    ):
        if logfile is None:
            logfile = sp.PIPE
        self.logfile = logfile
        self.filename = filename
        self.codec = codec
        self.ext = self.filename.split(".")[-1]

        # order is important
        cmd = [
            FFMPEG_BINARY,
            "-y",
            "-loglevel",
            "error" if logfile == sp.PIPE else "info",
            "-f",
            "s%dle" % (8 * nbytes),
            "-acodec",
            "pcm_s%dle" % (8 * nbytes),
            "-ar",
            "%d" % fps_input,
            "-ac",
            "%d" % nchannels,
            "-i",
            "-",
        ]
        if input_video is None:
            cmd.extend(["-vn"])
        else:
            cmd.extend(["-i", input_video, "-vcodec", "copy"])

        cmd.extend(["-acodec", codec] + ["-ar", "%d" % fps_input])
        cmd.extend(["-strict", "-2"])  # needed to support codec 'aac'
        if bitrate is not None:
            cmd.extend(["-ab", bitrate])
        if ffmpeg_params is not None:
            cmd.extend(ffmpeg_params)
        cmd.extend([filename])

        popen_params = cross_platform_popen_params({
            "stdout": sp.DEVNULL,
            "stderr": logfile,
            "stdin": sp.PIPE
        })

        self.proc = sp.Popen(cmd, **popen_params)
コード例 #4
0
def try_cmd(cmd):
    """TODO: add documentation"""
    try:
        popen_params = cross_platform_popen_params(
            {"stdout": sp.PIPE, "stderr": sp.PIPE, "stdin": sp.DEVNULL}
        )
        proc = sp.Popen(cmd, **popen_params)
        proc.communicate()
    except Exception as err:
        return False, err
    else:
        return True, None
コード例 #5
0
    def initialize(self, start_time=0):
        """Opens the file, creates the pipe."""
        self.close()  # if any

        if start_time != 0:
            offset = min(1, start_time)
            i_arg = [
                "-ss",
                "%.05f" % (start_time - offset),
                "-i",
                self.filename,
                "-vn",
                "-ss",
                "%.05f" % offset,
            ]
        else:
            i_arg = ["-i", self.filename, "-vn"]

        cmd = (
            [FFMPEG_BINARY]
            + i_arg
            + [
                "-loglevel",
                "error",
                "-f",
                self.format,
                "-acodec",
                self.codec,
                "-ar",
                "%d" % self.fps,
                "-ac",
                "%d" % self.nchannels,
                "-",
            ]
        )

        popen_params = cross_platform_popen_params(
            {
                "bufsize": self.buffersize,
                "stdout": sp.PIPE,
                "stderr": sp.PIPE,
                "stdin": sp.DEVNULL,
            }
        )

        self.proc = sp.Popen(cmd, **popen_params)

        self.pos = np.round(self.fps * start_time)
コード例 #6
0
def ffmpeg_write_image(filename, image, logfile=False, pixel_format=None):
    """Writes an image (HxWx3 or HxWx4 numpy array) to a file, using
    ffmpeg."""

    if image.dtype != "uint8":
        image = image.astype("uint8")
    if not pixel_format:
        pixel_format = "rgba" if (image.shape[2] == 4) else "rgb24"

    cmd = [
        FFMPEG_BINARY,
        "-y",
        "-s",
        "%dx%d" % (image.shape[:2][::-1]),
        "-f",
        "rawvideo",
        "-pix_fmt",
        pixel_format,
        "-i",
        "-",
        filename,
    ]

    if logfile:
        log_file = open(filename + ".log", "w+")
    else:
        log_file = sp.PIPE

    popen_params = cross_platform_popen_params({
        "stdout": sp.DEVNULL,
        "stderr": log_file,
        "stdin": sp.PIPE
    })

    proc = sp.Popen(cmd, **popen_params)
    out, err = proc.communicate(image.tostring())

    if proc.returncode:
        error = (
            f"{err}\n\nMoviePy error: FFMPEG encountered the following error while "
            f"writing file {filename} with command {cmd}:\n\n {err.decode()}")

        raise IOError(error)

    del proc
コード例 #7
0
def ffmpeg_parse_infos(
    filename,
    check_duration=True,
    fps_source="fps",
    decode_file=False,
    print_infos=False,
):
    """Get the information of a file using ffmpeg.

    Returns a dictionary with next fields:

    - ``"duration"``
    - ``"metadata"``
    - ``"inputs"``
    - ``"video_found"``
    - ``"video_fps"``
    - ``"video_n_frames"``
    - ``"video_duration"``
    - ``"video_bitrate"``
    - ``"video_metadata"``
    - ``"audio_found"``
    - ``"audio_fps"``
    - ``"audio_bitrate"``
    - ``"audio_metadata"``

    Note that "video_duration" is slightly smaller than "duration" to avoid
    fetching the uncomplete frames at the end, which raises an error.

    Parameters
    ----------

    filename
      Name of the file parsed, only used to raise accurate error messages.

    infos
      Information returned by FFmpeg.

    fps_source
      Indicates what source data will be preferably used to retrieve fps data.

    check_duration
      Enable or disable the parsing of the duration of the file. Useful to
      skip the duration check, for example, for images.

    decode_file
      Indicates if the whole file must be read to retrieve their duration.
      This is needed for some files in order to get the correct duration (see
      https://github.com/Zulko/moviepy/pull/1222).
    """
    # Open the file in a pipe, read output
    cmd = [FFMPEG_BINARY, "-hide_banner", "-i", filename]
    if decode_file:
        cmd.extend(["-f", "null", "-"])

    popen_params = cross_platform_popen_params({
        "bufsize": 10**5,
        "stdout": sp.PIPE,
        "stderr": sp.PIPE,
        "stdin": sp.DEVNULL,
    })

    proc = sp.Popen(cmd, **popen_params)
    (output, error) = proc.communicate()
    infos = error.decode("utf8", errors="ignore")

    proc.terminate()
    del proc

    if print_infos:
        # print the whole info text returned by FFMPEG
        print(infos)

    try:
        return FFmpegInfosParser(
            infos,
            filename,
            fps_source=fps_source,
            check_duration=check_duration,
            decode_file=decode_file,
        ).parse()
    except Exception as exc:
        if os.path.isdir(filename):
            raise IsADirectoryError(f"'{filename}' is a directory")
        elif not os.path.exists(filename):
            raise FileNotFoundError(f"'{filename}' not found")
        raise IOError(
            f"Error pasing `ffmpeg -i` command output:\n\n{infos}") from exc
コード例 #8
0
def ffmpeg_parse_infos(
    filename,
    decode_file=False,
    print_infos=False,
    check_duration=True,
    fps_source="fps",
):
    """Get file infos using ffmpeg.

    Returns a dictionnary with the fields:
    "video_found", "video_fps", "duration", "video_nframes",
    "video_duration", "video_bitrate","audio_found", "audio_fps", "audio_bitrate"

    "video_duration" is slightly smaller than "duration" to avoid
    fetching the uncomplete frames at the end, which raises an error.

    """
    # Open the file in a pipe, read output
    cmd = [FFMPEG_BINARY, "-i", filename]
    if decode_file:
        cmd.extend(["-f", "null", "-"])

    popen_params = cross_platform_popen_params(
        {
            "bufsize": 10 ** 5,
            "stdout": sp.PIPE,
            "stderr": sp.PIPE,
            "stdin": sp.DEVNULL,
        }
    )

    proc = sp.Popen(cmd, **popen_params)
    (output, error) = proc.communicate()
    infos = error.decode("utf8", errors="ignore")

    proc.terminate()
    del proc

    if print_infos:
        # print the whole info text returned by FFMPEG
        print(infos)

    lines = infos.splitlines()
    if "No such file or directory" in lines[-1]:
        raise IOError(
            (
                "MoviePy error: the file %s could not be found!\n"
                "Please check that you entered the correct "
                "path."
            )
            % filename
        )

    result = dict()

    # get duration (in seconds)
    result["duration"] = None

    if check_duration:
        try:
            if decode_file:
                line = [line for line in lines if "time=" in line][-1]
            else:
                line = [line for line in lines if "Duration:" in line][-1]
            match = re.findall("([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9])", line)[0]
            result["duration"] = convert_to_seconds(match)
        except Exception:
            raise IOError(
                f"MoviePy error: failed to read the duration of file {filename}.\n"
                f"Here are the file infos returned by ffmpeg:\n\n{infos}"
            )

    # get the output line that speaks about video
    lines_video = [
        line for line in lines if " Video: " in line and re.search(r"\d+x\d+", line)
    ]

    result["video_found"] = lines_video != []

    if result["video_found"]:
        try:
            line = lines_video[0]

            # get the size, of the form 460x320 (w x h)
            match = re.search(" [0-9]*x[0-9]*(,| )", line)
            size = list(map(int, line[match.start() : match.end() - 1].split("x")))
            result["video_size"] = size
        except Exception:
            raise IOError(
                (
                    "MoviePy error: failed to read video dimensions in file %s.\n"
                    "Here are the file infos returned by ffmpeg:\n\n%s"
                )
                % (filename, infos)
            )
        match_bit = re.search(r"(\d+) kb/s", line)
        result["video_bitrate"] = int(match_bit.group(1)) if match_bit else None

        # Get the frame rate. Sometimes it's 'tbr', sometimes 'fps', sometimes
        # tbc, and sometimes tbc/2...
        # Current policy: Trust fps first, then tbr unless fps_source is
        # specified as 'tbr' in which case try tbr then fps

        # If result is near from x*1000/1001 where x is 23,24,25,50,
        # replace by x*1000/1001 (very common case for the fps).

        def get_tbr():
            match = re.search("( [0-9]*.| )[0-9]* tbr", line)

            # Sometimes comes as e.g. 12k. We need to replace that with 12000.
            s_tbr = line[match.start() : match.end()].split(" ")[1]
            if "k" in s_tbr:
                tbr = float(s_tbr.replace("k", "")) * 1000
            else:
                tbr = float(s_tbr)
            return tbr

        def get_fps():
            match = re.search("( [0-9]*.| )[0-9]* fps", line)
            fps = float(line[match.start() : match.end()].split(" ")[1])
            return fps

        if fps_source == "tbr":
            try:
                result["video_fps"] = get_tbr()
            except Exception:
                result["video_fps"] = get_fps()

        elif fps_source == "fps":
            try:
                result["video_fps"] = get_fps()
            except Exception:
                result["video_fps"] = get_tbr()

        # It is known that a fps of 24 is often written as 24000/1001
        # but then ffmpeg nicely rounds it to 23.98, which we hate.
        coef = 1000.0 / 1001.0
        fps = result["video_fps"]
        for x in [23, 24, 25, 30, 50]:
            if (fps != x) and abs(fps - x * coef) < 0.01:
                result["video_fps"] = x * coef

        if check_duration:
            result["video_nframes"] = int(result["duration"] * result["video_fps"])
            result["video_duration"] = result["duration"]
        else:
            result["video_nframes"] = 1
            result["video_duration"] = None
        # We could have also recomputed the duration from the number
        # of frames, as follows:
        # >>> result['video_duration'] = result['video_nframes'] / result['video_fps']

        # get the video rotation info.
        try:
            rotation_lines = [
                line
                for line in lines
                if "rotate          :" in line and re.search(r"\d+$", line)
            ]
            if len(rotation_lines):
                rotation_line = rotation_lines[0]
                match = re.search(r"\d+$", rotation_line)
                result["video_rotation"] = int(
                    rotation_line[match.start() : match.end()]
                )
            else:
                result["video_rotation"] = 0
        except Exception:
            raise IOError(
                (
                    "MoviePy error: failed to read video rotation in file %s.\n"
                    "Here are the file infos returned by ffmpeg:\n\n%s"
                )
                % (filename, infos)
            )

    lines_audio = [line for line in lines if " Audio: " in line]

    result["audio_found"] = lines_audio != []

    if result["audio_found"]:
        line = lines_audio[0]
        try:
            match = re.search(" [0-9]* Hz", line)
            hz_string = line[
                match.start() + 1 : match.end() - 3
            ]  # Removes the 'hz' from the end
            result["audio_fps"] = int(hz_string)
        except Exception:
            result["audio_fps"] = "unknown"
        match_bit = re.search(r"(\d+) kb/s", line)
        result["audio_bitrate"] = int(match_bit.group(1)) if match_bit else None

    return result
コード例 #9
0
def write_gif(
    clip,
    filename,
    fps=None,
    with_mask=True,
    program="ImageMagick",
    opt="OptimizeTransparency",
    fuzz=1,
    loop=0,
    dispose=True,
    colors=None,
    pixel_format=None,
    logger="bar",
):
    """Write the VideoClip to a GIF file, without temporary files.

    Converts a VideoClip into an animated GIF using ImageMagick
    or ffmpeg.


    Parameters
    ----------

    clip : moviepy.video.VideoClip.VideoClip
      The clip from which the frames will be extracted to create the GIF image.

    filename : str
      Name of the resulting gif file.

    fps : int, optional
      Number of frames per second. If it isn't provided, then the function will
      look for the clip's ``fps`` attribute.

    with_mask : bool, optional
      Includes tha mask of the clip in the output (the clip must have a mask
      if this argument is ``True``).

    program : str, optional
      Software to use for the conversion, either ``"ImageMagick"`` or
      ``"ffmpeg"``.

    opt : str, optional
      ImageMagick only optimalization to apply, either ``"optimizeplus"`` or
      ``"OptimizeTransparency"``. Doesn't takes effect if ``program="ffmpeg"``.

    fuzz : float, optional
      ImageMagick only compression option which compresses the GIF by
      considering that the colors that are less than ``fuzz`` different are in
      fact the same.

    loop : int, optional
      Repeat the clip using ``loop`` iterations in the resulting GIF.

    dispose : bool, optional
      ImageMagick only option which, when enabled, the ImageMagick binary will
      take the argument `-dispose 2`, clearing the frame area with the
      background color, otherwise it will be defined as ``-dispose 1`` which
      will not dispose, just overlays next frame image.

    colors : int, optional
      ImageMagick only option for color reduction. Defines the maximum number
      of colors that the output image will have.

    pixel_format : str, optional
      FFmpeg pixel format for the output gif file. If is not specified
      ``"rgb24"`` will be used as the default format unless ``clip.mask``
      exist, then ``"rgba"`` will be used. Doesn't takes effect if
      ``program="ImageMagick"``.

    logger : str, optional
      Either ``"bar"`` for progress bar or ``None`` or any Proglog logger.


    Examples
    --------

    The gif will be playing the clip in real time, you can only change the
    frame rate. If you want the gif to be played slower than the clip you will
    use:

    >>> # slow down clip 50% and make it a GIF
    >>> myClip.multiply_speed(0.5).write_gif('myClip.gif')
    """
    #
    # We use processes chained with pipes.
    #
    # if program == 'ffmpeg'
    # frames --ffmpeg--> gif
    #
    # if program == 'ImageMagick' and optimize == (None, False)
    # frames --ffmpeg--> bmp frames --ImageMagick--> gif
    #
    #
    # if program == 'ImageMagick' and optimize != (None, False)
    # frames -ffmpeg-> bmp frames -ImagMag-> gif -ImagMag-> better gif
    #

    delay = 100.0 / fps
    logger = proglog.default_bar_logger(logger)
    if clip.mask is None:
        with_mask = False
    if not pixel_format:
        pixel_format = "rgba" if with_mask else "rgb24"

    cmd1 = [
        FFMPEG_BINARY,
        "-y",
        "-loglevel",
        "error",
        "-f",
        "rawvideo",
        "-vcodec",
        "rawvideo",
        "-r",
        "%.02f" % fps,
        "-s",
        "%dx%d" % (clip.w, clip.h),
        "-pix_fmt",
        (pixel_format),
        "-i",
        "-",
    ]

    popen_params = cross_platform_popen_params({
        "stdout": sp.DEVNULL,
        "stderr": sp.DEVNULL,
        "stdin": sp.DEVNULL
    })

    if program == "ffmpeg":
        if loop:
            clip = loop_fx(clip, n=loop)

        popen_params["stdin"] = sp.PIPE
        popen_params["stdout"] = sp.DEVNULL

        proc1 = sp.Popen(
            cmd1 + [
                "-pix_fmt",
                (pixel_format),
                "-r",
                "%.02f" % fps,
                filename,
            ],
            **popen_params,
        )
    else:

        popen_params["stdin"] = sp.PIPE
        popen_params["stdout"] = sp.PIPE

        proc1 = sp.Popen(cmd1 + ["-f", "image2pipe", "-vcodec", "bmp", "-"],
                         **popen_params)

    if program == "ImageMagick":

        cmd2 = [
            IMAGEMAGICK_BINARY,
            "-delay",
            "%.02f" % (delay),
            "-dispose",
            "%d" % (2 if dispose else 1),
            "-loop",
            "%d" % loop,
            "-",
            "-coalesce",
        ]

        if opt in [False, None]:
            popen_params["stdin"] = proc1.stdout
            popen_params["stdout"] = sp.DEVNULL
            proc2 = sp.Popen(cmd2 + [filename], **popen_params)

        else:
            popen_params["stdin"] = proc1.stdout
            popen_params["stdout"] = sp.PIPE
            proc2 = sp.Popen(cmd2 + ["gif:-"], **popen_params)

        if opt:

            cmd3 = ([
                IMAGEMAGICK_BINARY,
                "-",
                "-fuzz",
                "%d" % fuzz + "%",
                "-layers",
                opt,
            ] + (["-colors", "%d" % colors] if colors is not None else []) +
                    [filename])

            popen_params["stdin"] = proc2.stdout
            popen_params["stdout"] = sp.DEVNULL
            proc3 = sp.Popen(cmd3, **popen_params)

    # We send all the frames to the first process
    logger(message="MoviePy - Building file  %s" % filename)
    logger(message="MoviePy - - Generating GIF frames.")
    try:
        for t, frame in clip.iter_frames(fps=fps,
                                         logger=logger,
                                         with_times=True,
                                         dtype="uint8"):
            if with_mask:
                mask = 255 * clip.mask.get_frame(t)
                frame = np.dstack([frame, mask]).astype("uint8")
            proc1.stdin.write(frame.tobytes())

    except IOError as err:

        error = ("[MoviePy] Error: creation of %s failed because "
                 "of the following error:\n\n%s.\n\n." % (filename, str(err)))

        if program == "ImageMagick":
            error += (
                "This can be due to the fact that "
                "ImageMagick is not installed on your computer, or "
                "(for Windows users) that you didn't specify the "
                "path to the ImageMagick binary. Check the documentation.")

        raise IOError(error)
    if program == "ImageMagick":
        logger(message="MoviePy - - Optimizing GIF with ImageMagick.")
    proc1.stdin.close()
    proc1.wait()
    if program == "ImageMagick":
        proc2.wait()
        if opt:
            proc3.wait()
    logger(message="MoviePy - - File ready: %s." % filename)
コード例 #10
0
ファイル: ffmpeg_writer.py プロジェクト: tombaileywzd/moviepy
    def __init__(
        self,
        filename,
        size,
        fps,
        codec="libx264",
        audiofile=None,
        preset="medium",
        bitrate=None,
        with_mask=False,
        logfile=None,
        threads=None,
        ffmpeg_params=None,
        pixel_format=None,
    ):
        if logfile is None:
            logfile = sp.PIPE
        self.logfile = logfile
        self.filename = filename
        self.codec = codec
        self.ext = self.filename.split(".")[-1]
        if not pixel_format:  # pragma: no cover
            pixel_format = "rgba" if with_mask else "rgb24"

        # order is important
        cmd = [
            FFMPEG_BINARY,
            "-y",
            "-loglevel",
            "error" if logfile == sp.PIPE else "info",
            "-f",
            "rawvideo",
            "-vcodec",
            "rawvideo",
            "-s",
            "%dx%d" % (size[0], size[1]),
            "-pix_fmt",
            pixel_format,
            "-r",
            "%.02f" % fps,
            "-an",
            "-i",
            "-",
        ]
        if audiofile is not None:
            cmd.extend(["-i", audiofile, "-acodec", "copy"])
        cmd.extend(["-vcodec", codec, "-preset", preset])
        if ffmpeg_params is not None:
            cmd.extend(ffmpeg_params)
        if bitrate is not None:
            cmd.extend(["-b", bitrate])

        if threads is not None:
            cmd.extend(["-threads", str(threads)])

        if (codec == "libx264") and (size[0] % 2 == 0) and (size[1] % 2 == 0):
            cmd.extend(["-pix_fmt", "yuv420p"])
        cmd.extend([filename])

        popen_params = cross_platform_popen_params({
            "stdout": sp.DEVNULL,
            "stderr": logfile,
            "stdin": sp.PIPE
        })

        self.proc = sp.Popen(cmd, **popen_params)
コード例 #11
0
ファイル: ffmpeg_writer.py プロジェクト: tombaileywzd/moviepy
def ffmpeg_write_image(filename, image, logfile=False, pixel_format=None):
    """Writes an image (HxWx3 or HxWx4 numpy array) to a file, using ffmpeg.

    Parameters
    ----------

    filename : str
        Path to the output file.

    image : np.ndarray
        Numpy array with the image data.

    logfile : bool, optional
        Writes the ffmpeg output inside a logging file (``True``) or not
        (``False``).

    pixel_format : str, optional
        Pixel format for ffmpeg. If not defined, it will be discovered checking
        if the image data contains an alpha channel (``"rgba"``) or not
        (``"rgb24"``).
    """
    if image.dtype != "uint8":
        image = image.astype("uint8")
    if not pixel_format:
        pixel_format = "rgba" if (image.shape[2] == 4) else "rgb24"

    cmd = [
        FFMPEG_BINARY,
        "-y",
        "-s",
        "%dx%d" % (image.shape[:2][::-1]),
        "-f",
        "rawvideo",
        "-pix_fmt",
        pixel_format,
        "-i",
        "-",
        filename,
    ]

    if logfile:
        log_file = open(filename + ".log", "w+")
    else:
        log_file = sp.PIPE

    popen_params = cross_platform_popen_params({
        "stdout": sp.DEVNULL,
        "stderr": log_file,
        "stdin": sp.PIPE
    })

    proc = sp.Popen(cmd, **popen_params)
    out, err = proc.communicate(image.tobytes())

    if proc.returncode:
        error = (
            f"{err}\n\nMoviePy error: FFMPEG encountered the following error while "
            f"writing file {filename} with command {cmd}:\n\n {err.decode()}")

        raise IOError(error)

    del proc
コード例 #12
0
ファイル: gif_writers.py プロジェクト: ii0/moviepy
def write_gif(
    clip,
    filename,
    fps=None,
    program="ImageMagick",
    opt="OptimizeTransparency",
    fuzz=1,
    with_mask=True,
    loop=0,
    dispose=True,
    colors=None,
    logger="bar",
    pixel_format=None,
):
    """Write the VideoClip to a GIF file, without temporary files.

    Converts a VideoClip into an animated GIF using ImageMagick
    or ffmpeg.


    Parameters
    -----------

    filename
      Name of the resulting gif file.

    fps
      Number of frames per second (see note below). If it
        isn't provided, then the function will look for the clip's
        ``fps`` attribute (VideoFileClip, for instance, have one).

    program
      Software to use for the conversion, either 'ImageMagick' or
      'ffmpeg'.

    opt
      (ImageMagick only) optimalization to apply, either
      'optimizeplus' or 'OptimizeTransparency'.

    fuzz
      (ImageMagick only) Compresses the GIF by considering that
      the colors that are less than fuzz% different are in fact
      the same.

    pixel_format
      Pixel format for the output gif file. If is not specified
      'rgb24' will be used as the default format unless ``clip.mask``
      exist, then 'rgba' will be used. This option is going to
      be ignored if ``program=ImageMagick``.


    Notes
    -----

    The gif will be playing the clip in real time (you can
    only change the frame rate). If you want the gif to be played
    slower than the clip you will use ::

        >>> # slow down clip 50% and make it a gif
        >>> myClip.speedx(0.5).write_gif('myClip.gif')

    """

    #
    # We use processes chained with pipes.
    #
    # if program == 'ffmpeg'
    # frames --ffmpeg--> gif
    #
    # if program == 'ImageMagick' and optimize == (None, False)
    # frames --ffmpeg--> bmp frames --ImageMagick--> gif
    #
    #
    # if program == 'ImageMagick' and optimize != (None, False)
    # frames -ffmpeg-> bmp frames -ImagMag-> gif -ImagMag-> better gif
    #

    delay = 100.0 / fps
    logger = proglog.default_bar_logger(logger)
    if clip.mask is None:
        with_mask = False
    if not pixel_format:
        pixel_format = "rgba" if with_mask else "rgb24"

    cmd1 = [
        FFMPEG_BINARY,
        "-y",
        "-loglevel",
        "error",
        "-f",
        "rawvideo",
        "-vcodec",
        "rawvideo",
        "-r",
        "%.02f" % fps,
        "-s",
        "%dx%d" % (clip.w, clip.h),
        "-pix_fmt",
        (pixel_format),
        "-i",
        "-",
    ]

    popen_params = cross_platform_popen_params({
        "stdout": sp.DEVNULL,
        "stderr": sp.DEVNULL,
        "stdin": sp.DEVNULL
    })

    if program == "ffmpeg":
        popen_params["stdin"] = sp.PIPE
        popen_params["stdout"] = sp.DEVNULL

        proc1 = sp.Popen(
            cmd1 + [
                "-pix_fmt",
                (pixel_format),
                "-r",
                "%.02f" % fps,
                filename,
            ],
            **popen_params,
        )
    else:

        popen_params["stdin"] = sp.PIPE
        popen_params["stdout"] = sp.PIPE

        proc1 = sp.Popen(cmd1 + ["-f", "image2pipe", "-vcodec", "bmp", "-"],
                         **popen_params)

    if program == "ImageMagick":

        cmd2 = [
            IMAGEMAGICK_BINARY,
            "-delay",
            "%.02f" % (delay),
            "-dispose",
            "%d" % (2 if dispose else 1),
            "-loop",
            "%d" % loop,
            "-",
            "-coalesce",
        ]

        if opt in [False, None]:
            popen_params["stdin"] = proc1.stdout
            popen_params["stdout"] = sp.DEVNULL
            proc2 = sp.Popen(cmd2 + [filename], **popen_params)

        else:
            popen_params["stdin"] = proc1.stdout
            popen_params["stdout"] = sp.PIPE
            proc2 = sp.Popen(cmd2 + ["gif:-"], **popen_params)

        if opt:

            cmd3 = ([
                IMAGEMAGICK_BINARY,
                "-",
                "-fuzz",
                "%d" % fuzz + "%",
                "-layers",
                opt,
            ] + (["-colors", "%d" % colors] if colors is not None else []) +
                    [filename])

            popen_params["stdin"] = proc2.stdout
            popen_params["stdout"] = sp.DEVNULL
            proc3 = sp.Popen(cmd3, **popen_params)

    # We send all the frames to the first process
    logger(message="MoviePy - Building file  %s" % filename)
    logger(message="MoviePy - - Generating GIF frames.")
    try:
        for t, frame in clip.iter_frames(fps=fps,
                                         logger=logger,
                                         with_times=True,
                                         dtype="uint8"):
            if with_mask:
                mask = 255 * clip.mask.get_frame(t)
                frame = np.dstack([frame, mask]).astype("uint8")
            proc1.stdin.write(frame.tobytes())

    except IOError as err:

        error = ("[MoviePy] Error: creation of %s failed because "
                 "of the following error:\n\n%s.\n\n." % (filename, str(err)))

        if program == "ImageMagick":
            error += (
                "This can be due to the fact that "
                "ImageMagick is not installed on your computer, or "
                "(for Windows users) that you didn't specify the "
                "path to the ImageMagick binary. Check the documentation.")

        raise IOError(error)
    if program == "ImageMagick":
        logger(message="MoviePy - - Optimizing GIF with ImageMagick.")
    proc1.stdin.close()
    proc1.wait()
    if program == "ImageMagick":
        proc2.wait()
        if opt:
            proc3.wait()
    logger(message="MoviePy - - File ready: %s." % filename)