Ejemplo n.º 1
0
    def unoptimize_gif(cls, gif_path: Path, out_path: Path) -> Path:
        """Perform GIF unoptimization using Gifsicle/ImageMagick, in order to obtain the true singular frames for
        Splitting purposes. Returns the path of the unoptimized GIF.

        Args:
            gif_path (Path): Path to GIF image
            out_path (Path): Output path of unoptimized GIF

        Returns:
            Path: Path of unoptimized GIF
        """
        # raise Exception(gif_path, out_dir)
        supressed_error_txts = ["binary operator expected"]
        args = [
            str(cls.imagemagick_path), "convert", "-coalesce",
            str(gif_path),
            str(out_path)
        ]
        cmd = " ".join(args)
        stdio.debug(cmd)
        process = subprocess.Popen(args,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
        while process.poll() is None:
            if process.stdout:
                stdout_res = process.stdout.readline().decode("utf-8")
                if stdout_res:
                    stdio.message(stdout_res)
            if process.stderr:
                stderr_res = process.stderr.readline().decode("utf-8")
                if stderr_res and not any(s in stderr_res
                                          for s in supressed_error_txts):
                    stdio.error(stderr_res)
        return out_path
Ejemplo n.º 2
0
def create_aimg(image_paths: List[Path], out_path: Path, crbundle: CriteriaBundle) -> Path:
    """ Umbrella generator for creating animated images from a sequence of images """
    # abs_image_paths = [os.path.abspath(ip) for ip in image_paths if os.path.exists(ip)]
    img_paths = [f for f in image_paths if str.lower(f.suffix[1:]) in STATIC_IMG_EXTS]
    # workpath = os.path.dirname(img_paths[0])
    # Test if inputted filename has extension, then remove it from the filename
    img_format = crbundle.create_aimg_criteria.format
    if len(img_paths) < 2:
        raise Exception(f"At least 2 images is needed for an animated {img_format}!")
    # if not out_dir:
    #     raise Exception("No output folder selected, please select it first")
    # out_dir = os.path.abspath(out_dir)
    # if not os.path.exists(out_dir):
    #     raise Exception(f"The specified absolute out_dir does not exist!\n{out_dir}")

    if img_format.casefold() == "gif":
        # out_full_path = out_dir.joinpath(f"{filename}.gif")
        # filename = f"{filename}.gif"
        out_full_path = create_animated_gif(img_paths, out_path, crbundle)
        return out_full_path
        # return _build_gif(img_paths, out_path, crbundle)

    elif img_format.casefold() == "png":
        # out_full_path = out_dir.joinpath(f"{filename}.png")
        return _build_apng(img_paths, out_path, crbundle)
        # return _build_apng(img_paths, out_full_path, crbundle)
    else:
        stdio.error(f"The image format {img_format} is not supported")
Ejemplo n.º 3
0
def inspect_sequence(image_paths: List[Path]) -> Dict:
    """Gets information of a selected sequence of static images

    Args:
        image_paths (List[Path]): List of resolved image sequence paths

    Returns:
        Dict: Information of the image sequence as a whole
    """
    abs_image_paths = image_paths
    sequence_info = []
    shout_nums = imageutils.shout_indices(len(abs_image_paths), 1)
    for index, path in enumerate(abs_image_paths):
        if shout_nums.get(index):
            stdio.message(f"Loading images... ({shout_nums.get(index)})")
        info = inspect_general(path, filter_on="static", skip=True)
        if info:
            gen_info = info.format_info()["general_info"]
            sequence_info.append(gen_info)
    # logger.message(sequence_info)
    if not sequence_info:
        stdio.error(
            "No images selected. Make sure the path to them are correct and they are not animated images!"
        )
        return {}
    static_img_paths = [si["absolute_url"]["value"] for si in sequence_info]
    # print("imgs count", len(static_img_paths))
    # first_img_name = os.path.splitext(os.path.basename(static_img_paths[0]))[0]
    # filename = first_img_name.split('_')[0] if '_' in first_img_name else first_img_name
    sequence_count = len(static_img_paths)
    sequence_filesize = filehandler.read_filesize(
        sum((si["fsize"]["value"] for si in sequence_info)))
    # im = Image.open(static_img_paths[0])
    # width, height = im.size
    # im.close()
    image_info = {
        "name": sequence_info[0]["base_filename"]["value"],
        "total": sequence_count,
        "sequence": static_img_paths,
        "sequence_info": sequence_info,
        "total_size": sequence_filesize,
        "width": sequence_info[0]["width"]["value"],
        "height": sequence_info[0]["height"]["value"],
    }
    return image_info
Ejemplo n.º 4
0
    def optimize_apng(
        cls,
        target_path: Path,
        out_full_path: Path,
        apngopt_criteria: APNGOptimizationCriteria,
    ) -> Path:
        """Use apngopt to optimize an APNG. Returns the output path

        Args:
            aopt_args (List[Tuple[str, str]]): apngopt arguments
            target_path (Path): Target path of the animated image to be optimized
            out_full_path (Path): Destination output path to save the optimized image to
            total_ops (int, optional): UNUSED. Defaults to 0.
            shift_index (int, optional): UNUSED. Defaults to 0.

        Returns:
            Path: [description]
        """
        aopt_args = cls._opt_args_builder(apngopt_criteria)
        aopt_dir = filehandler.mk_cache_dir(prefix_name="apngopt_dir")
        filename = target_path.name
        aopt_temp_path = aopt_dir.joinpath(filename)
        # logger.message(f"COPY FROM {target_path} TO {aopt_temp_path}")
        target_path = shutil.copy(target_path,
                                  aopt_temp_path,
                                  follow_symlinks=False)
        cwd = os.getcwd()
        # common_path = os.path.commonpath([opt_exec_path, target_path])
        target_rel_path = Path(os.path.relpath(target_path, cwd))
        # out_rel_path = Path(os.path.relpath(out_full_path, cwd))
        newline_check = ["\r\n", "\n"]
        for index, (arg, description) in enumerate(aopt_args, start=1):
            stdio.message(
                f"index {index}, arg {arg}, description: {description}")
            cmdlist = [
                str(cls.opt_exec_path),
                arg,
                str(target_rel_path),
                str(target_rel_path),
            ]
            # raise Exception(cmdlist, out_full_path)
            cmd = " ".join(cmdlist)
            # result = subprocess.check_output(cmd, shell=True)
            stdio.message("Performing optimization...")
            stdio.debug(cmd)
            stdio.debug(cmdlist)
            process = subprocess.Popen(cmdlist,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            index = 0
            while process.poll() is None:
                # if process.poll() is not None:
                # break
                # print('process-polling...')
                if process.stdout:
                    # print(process.stdout)
                    stdout_res = process.stdout.readline().decode("utf-8")
                    # print('1')
                    stdio.debug(stdout_res.capitalize())
                    # print('2')
                    if stdout_res and stdout_res not in newline_check and "saving" in stdout_res:
                        # print('3')
                        out_words = " ".join(
                            stdout_res.translate({
                                ord("\r"): None,
                                ord("\n"): None
                            }).capitalize().split(" ")[3:])[:-1]
                        out_msg = f"Optimizing frame {out_words}..."
                        stdio.message(out_msg)
                        index += 1
                        # print('4')
                    # print('5')
                elif process.stderr:
                    # print('6')
                    stderr_res = process.stderr.readline().decode("utf-8")
                    # print('7')
                    # if stderr_res and not any(s in stderr_res for s in supressed_error_txts):
                    if stderr_res:
                        # print('8')
                        stdio.error(stderr_res)
                    # print('9')
                # process.communicate('\n')
            # if target_path != out_full_path:
            # target_path = out_full_path
        out_full_path = shutil.move(target_path, out_full_path)
        shutil.rmtree(aopt_dir)
        return out_full_path
Ejemplo n.º 5
0
    def modify_gif_image(cls, target_path: Path, out_full_path: Path,
                         original_metadata: AnimatedImageMetadata,
                         crbundle: CriteriaBundle) -> Path:
        """Use gifsicle to perform an array of modifications on an existing GIF image, by looping through the supplied
        arguments.

        Args:
            target_path (Path): Target path of the existing GIF image.
            out_full_path (Path): Output full path to save the GIF to.
            original_metadata (AnimatedImageMetadata): Original GIF metadata
            crbundle (CriteriaBundle): Criteria bundle object

        Returns:
            Path: Resulting path of the new modified GIF image.
        """
        gifsicle_options = cls._mod_options_builder(
            original_metadata, crbundle.modify_aimg_criteria,
            crbundle.gif_opt_criteria)
        supressed_error_txts = [
            "warning: too many colors, using local colormaps",
            "You may want to try"
        ]
        # yield {"sicle_args": sicle_args}
        if os_platform() not in (OS.WINDOWS, OS.LINUX):
            raise UnsupportedPlatformException(platform)
        for index, (option, description) in enumerate(gifsicle_options,
                                                      start=1):
            # yield {"msg": f"index {index}, arg {arg}, description: {description}"}
            stdio.message(description)
            args = [
                shlex.quote(str(cls.gifsicle_path)) if os_platform()
                == OS.LINUX else str(cls.gifsicle_path), option,
                shlex.quote(str(target_path))
                if os_platform() == OS.LINUX else str(target_path), "--output",
                shlex.quote(str(out_full_path))
                if os_platform() == OS.LINUX else str(out_full_path)
            ]
            # cmd = " ".join(cmdlist)
            # yield {"msg": f"[{index}/{total_ops}] {description}"}
            # yield {"cmd": cmd}
            cmd = " ".join(args)
            if ";" in cmd:
                raise MalformedCommandException("gifsicle")
            stdio.debug(f"modify_gif_image cmd -> {cmd}")
            result = subprocess.Popen(
                args if os_platform() == OS.WINDOWS else cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                shell=os_platform() == OS.LINUX)
            while result.poll() is None:
                if result.stdout:
                    stdout_res = result.stdout.readline().decode("utf-8")
                    if stdout_res and not any(s in stdout_res
                                              for s in supressed_error_txts):
                        stdio.message(stdout_res)
                if result.stderr:
                    stderr_res = result.stderr.readline().decode("utf-8")
                    if stderr_res and not any(s in stderr_res
                                              for s in supressed_error_txts):
                        stdio.error(stderr_res)
            if target_path != out_full_path:
                target_path = out_full_path
        return target_path
Ejemplo n.º 6
0
    def combine_gif_images(cls, gifragment_dir: Path, out_full_path: Path,
                           crbundle: CriteriaBundle) -> Path:
        """Combine a list of static GIF images in a directory into one animated GIF image.

        Args:
            gifragment_dir (List[Path]): Path to the directory containing static GIF images.
            out_full_path (Path): Full path including the name of the new animated GIF image.
            crbundle (CriteriaBundle): Bundle of image creation criteria to adhere to.

        Returns:
            Path: Path of the created GIF.
        """
        ROOT_PATH = str(os.getcwd())
        if os.getcwd() != gifragment_dir:
            stdio.message(
                f"Changing directory from {os.getcwd()} to {gifragment_dir}")
            os.chdir(gifragment_dir)
        stdio.message("Combining frames...")
        supressed_error_txts = [
            "warning: too many colors, using local colormaps",
            "You may want to try",
            "input images have conflicting background colors",
            "This means some animation frames may appear incorrect."
        ]

        # result = subprocess.run(cmd, shell=True, capture_output=True)
        # stdout_res = result.stdout.decode("utf-8")
        # stderr_res = result.stderr.decode("utf-8")
        # logger.message(stdout_res)
        # if "gifsicle.exe: warning: too many colors, using local colormaps" not in stderr_res:
        #     logger.error(stderr_res)

        if os_platform() == OS.WINDOWS:
            args = cls._combine_cmd_builder(out_full_path,
                                            crbundle,
                                            quotes=False)
            stdio.debug(args)
            result = subprocess.Popen(args,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
        elif os_platform() == OS.LINUX:
            args = cls._combine_cmd_builder(out_full_path,
                                            crbundle,
                                            quotes=True)
            cmd = " ".join(args)
            stdio.debug(f"linux cmd -> {cmd}")
            result = subprocess.Popen(cmd,
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE,
                                      shell=True)
        else:
            raise UnsupportedPlatformException(platform)
        while result.poll() is None:
            if result.stdout:
                stdout_res = result.stdout.readline().decode("utf-8")
                if stdout_res and not any(s in stdout_res
                                          for s in supressed_error_txts):
                    stdio.message(stdout_res)
            if result.stderr:
                stderr_res = result.stderr.readline().decode("utf-8")
                if stderr_res and not any(s in stderr_res
                                          for s in supressed_error_txts):
                    stdio.error(stderr_res)

        os.chdir(ROOT_PATH)
        return out_full_path
Ejemplo n.º 7
0
def inspect_static_image(image_path: Path) -> ImageMetadata:
    """Returns all information regarding a single static image

    Args:
        image_path (Path): Path to the image file

    Returns:
        Dict: Information of the static image file
    """
    # image_info = {}
    # im: Image = None
    try:
        # if image_path.__class__.__bases__[0] is ImageFile.ImageFile:
        #     im = image_path
        # else:
        #     im = Image.open(image)
        im = Image.open(image_path)
    except Exception as e:
        stdio.error(str(e).replace("\\\\", "/"))
        return
    fmt = im.format
    exif = ""
    if fmt.upper() != "GIF":
        exif_raw = im._getexif()
        if exif_raw:
            exif = {
                ExifTags.TAGS[k]: v
                for k, v in exif_raw.items() if k in ExifTags.TAGS
            }
    width, height = im.size
    filename = image_path.name
    base_fname = image_path.stem
    ext = image_path.suffix
    base_fname = imageutils.sequence_nameget(base_fname)
    fsize = image_path.stat().st_size
    # fsize_hr = read_filesize(fsize)
    color_mode = COLOR_MODE_FULL_NAME.get(im.mode) or im.mode
    has_transparency = im.info.get(
        "transparency") is not None or im.mode == "RGBA"
    # alpha = im.getchannel('A')
    comment = im.info.get("comment", "")
    icc = im.info.get("icc_profile")
    color_profile = ""
    if icc:
        f = io.BytesIO(icc)
        color_profile = ImageCms.getOpenProfile(f).profile
        # print(color_profile.profile_description)
        stdio.debug({
            "copyright": color_profile.copyright,
            "technology": color_profile.technology,
            "manufacturer": color_profile.manufacturer,
            "creation_date": '',
            "header_manufacturer": color_profile.header_manufacturer,
            "header_model": color_profile.header_model,
            "icc_version": color_profile.icc_version,
            "target": color_profile.target
        })
        color_profile = color_profile.profile_description
    # if palette:
    #     logger.debug(imageutils.reshape_palette(palette))
    #     color_counts = np.array(im.getcolors())
    #     logger.debug(color_counts)
    #     logger.debug(color_counts.sum(axis=0)[0])
    # imageutils.get_palette_image(im).show()
    creation_dt = filehandler.get_creation_time(image_path)
    modification_dt = filehandler.get_modification_time(image_path)
    checksum = filehandler.hash_sha1(image_path)
    # logger.debug({"im.info": im.info, "icc": icc, "palette": imageutils.reshape_palette(palette) if palette else None})
    stdio.debug(im.info)
    if im.mode == "P":
        stdio.debug(im.getpalette())
    im.close()
    metadata = ImageMetadata({
        "name":
        filename,
        "base_filename":
        base_fname,
        "width":
        width,
        "height":
        height,
        "format":
        fmt,
        "fsize":
        fsize,
        "creation_datetime":
        creation_dt.strftime("%Y-%m-%d %H:%M:%S"),
        "modification_datetime":
        modification_dt.strftime("%Y-%m-%d %H:%M:%S"),
        "hash_sha1":
        checksum,
        "absolute_url":
        str(image_path),
        "comments":
        str(comment),
        "color_mode":
        str(color_mode),
        "color_profile":
        color_profile,
        "bit_depth":
        COLOR_MODE_BIT_DEPTH.get(im.mode),
        "has_transparency":
        has_transparency,
        "is_animated":
        False,
        "exif":
        str(exif),
    })
    return metadata