Skip to content

Commit

Permalink
Fonctionnalité : ajoute la possibilité de redimensionner des images s…
Browse files Browse the repository at this point in the history
…ans faire d'appel à un service externe (#130)
  • Loading branch information
Guts authored Aug 28, 2023
2 parents 04596dc + 5ebf45d commit f3d535d
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 29 deletions.
90 changes: 61 additions & 29 deletions geotribu_cli/images/images_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from geotribu_cli.__about__ import __package_name__
from geotribu_cli.console import console
from geotribu_cli.constants import GeotribuDefaults
from geotribu_cli.images.optim_pillow import PILLOW_INSTALLED, pil_redimensionner_image
from geotribu_cli.images.optim_tinify import TINIFY_INSTALLED, optimize_with_tinify
from geotribu_cli.utils.check_path import check_path
from geotribu_cli.utils.start_uri import open_uri
Expand Down Expand Up @@ -82,10 +83,12 @@ def parser_images_optimizer(
subparser.add_argument(
"-w",
"--with",
choices=["tinypng"],
default="tinypng",
choices=["local", "tinypng"],
default=getenv("GEOTRIBU_DEFAULT_IMAGE_OPTIMIZER", "tinypng"),
dest="tool_to_use",
help="Outil à utiliser pour réaliser l'optimisation.",
help="Outil à utiliser pour réaliser l'optimisation. Local (pillow), ou tinypng "
"(service distant nécessitant une clé d'API)",
metavar="GEOTRIBU_DEFAULT_IMAGE_OPTIMIZER",
)

subparser.set_defaults(func=run)
Expand All @@ -108,7 +111,29 @@ def run(args: argparse.Namespace):
"""
logger.debug(f"Running {args.command} with {args}")

# check Tinify API KEY
# liste l'image ou les images à optimiser
if check_path(
input_path=args.image_path,
must_be_a_folder=True,
must_be_a_file=False,
must_be_readable=True,
must_exists=True,
raise_error=False,
):
logger.info(f"Dossier d'images passé : {args.image_path}")
li_images = [
image.resolve()
for image in Path(args.image_path).glob("*")
if image.suffix.lower() in defaults_settings.images_body_extensions
]
if not li_images:
print(":person_shrugging: Aucune image trouvée dans {args.image_path}")
sys.exit(0)
else:
logger.debug(f"Image unique passée : {args.image_path}")
li_images = [args.image_path]

# Utilise l'outil d'optimisation
if args.tool_to_use == "tinypng":
if not TINIFY_INSTALLED:
logger.critical(
Expand All @@ -126,26 +151,6 @@ def run(args: argparse.Namespace):
)
sys.exit(1)

if check_path(
input_path=args.image_path,
must_be_a_folder=True,
must_be_a_file=False,
must_be_readable=True,
must_exists=True,
raise_error=False,
):
logger.info("Dossier d'images passé. L")
li_images = [
image.resolve()
for image in Path(args.image_path).glob("*")
if image.suffix.lower() in defaults_settings.images_body_extensions
]
if not li_images:
print(":person_shrugging: Aucune image trouvée dans {args.image_path}")
sys.exit(0)
else:
li_images = [args.image_path]

# optimize the image(s)
count_optim_success = 0
count_optim_error = 0
Expand All @@ -165,14 +170,41 @@ def run(args: argparse.Namespace):
f"{args.tool_to_use} a échoué. Trace : {err}"
)
count_optim_error += 1
elif args.tool_to_use == "local":
if not PILLOW_INSTALLED:
logger.critical(
"Pillow n'est pas installé. "
"Pour l'utiliser, installer l'outil avec les dépendances "
f"supplémentaires : pip install {__package_name__}[img-local] ou "
f"pip install {__package_name__}[all]"
)
sys.exit(1)

# open output folder if success and not disabled
if args.opt_auto_open_disabled and count_optim_success > 0:
open_uri(
in_filepath=defaults_settings.geotribu_working_folder.joinpath(
"images/optim"
# optimize the image(s)
count_optim_success = 0
count_optim_error = 0
for img in li_images:
try:
optimized_image = pil_redimensionner_image(image_path_or_url=img)
console.print(
f":clamp: L'image {img} a été redimensionnée et "
f"compressée avec {args.tool_to_use} : {optimized_image}"
)
count_optim_success += 1
except Exception as err:
logger.error(
f"La compression de l'image {img} avec "
f"{args.tool_to_use} a échoué. Trace : {err}"
)
count_optim_error += 1

# open output folder if success and not disabled
if args.opt_auto_open_disabled and count_optim_success > 0:
open_uri(
in_filepath=defaults_settings.geotribu_working_folder.joinpath(
"images/optim"
)
)


# -- Stand alone execution
Expand Down
93 changes: 93 additions & 0 deletions geotribu_cli/images/optim_pillow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#! python3 # noqa: E265

# ############################################################################
# ########## IMPORTS #############
# ################################

# standard library
import logging
from pathlib import Path

# 3rd party
try:
from PIL import Image
from PIL.ImageOps import contain

PILLOW_INSTALLED = True
except ImportError:
PILLOW_INSTALLED = False

# package
from geotribu_cli.constants import GeotribuDefaults

# ############################################################################
# ########## GLOBALS #############
# ################################

logger = logging.getLogger(__name__)
defaults_settings = GeotribuDefaults()

# ############################################################################
# ########## FUNCTIONS ###########
# ################################


def pil_redimensionner_image(
image_path_or_url: Path,
largeur_max_paysage: int = 1000,
hauteur_max_portrait: int = 600,
) -> Path:
"""Redimensionne l'image dont le chemin est passé en entrée en tenant compte d'une
contrainte de largeur max pour les images orientées paysage (largeur > hauteur) et
une hauteur max pour les images orientées portrait (hauteur > largeur).
Args:
image_path_or_url: chemin ou URL vers l'image
largeur_max_paysage: largeur maximum pour une image orientée paysage.
Defaults to 1000.
hauteur_max_portrait: hauteur maximum pour une image orientée portrait.
Defaults to 600.
Returns:
path to the resized image.
"""
# Ouvrir l'image
try:
img = Image.open(image_path_or_url)
except Exception as err:
logger.error(f"Impossible de lire l'image {image_path_or_url}. Trace : {err}")
return None

# Vérifier le rapport hauteur/largeur de l'image
rapport_hauteur_largeur = img.height / img.width

if rapport_hauteur_largeur > 1:
# L'image est en mode portrait
logger.info(f"{image_path_or_url} est orientée PORTRAIT")
nouvelle_hauteur = min(hauteur_max_portrait, img.height)
nouvelle_taille = (
int(nouvelle_hauteur / rapport_hauteur_largeur),
nouvelle_hauteur,
)
else:
logger.info(f"{image_path_or_url} est orientée PAYSAGE (ou carrée)")
# L'image est en mode paysage ou carré
nouvelle_largeur = min(largeur_max_paysage, img.width)
nouvelle_taille = (
nouvelle_largeur,
int(nouvelle_largeur * rapport_hauteur_largeur),
)

# Redimensionner l'image
new_img = contain(img, nouvelle_taille)

# sauvegarder l'image
output_filepath = defaults_settings.geotribu_working_folder.joinpath(
f"images/optim/{image_path_or_url.stem}_resized_{new_img.width}x"
f"{new_img.height}{image_path_or_url.suffix}"
)
output_filepath.parent.mkdir(parents=True, exist_ok=True)
new_img.save(output_filepath)
new_img.close()

return output_filepath
1 change: 1 addition & 0 deletions requirements/extra.img-local.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillow>=9.5,<10
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def load_requirements(requirements_files: Union[Path, list[Path]]) -> list:
"all": load_requirements(
list(HERE.joinpath("requirements").glob("extra.*.txt"))
),
"img-local": load_requirements(HERE / "requirements/extra.img-local.txt"),
"img-remote": load_requirements(HERE / "requirements/extra.img-remote.txt"),
},
# cli
Expand Down

0 comments on commit f3d535d

Please sign in to comment.