Resize images + convert any format (HEIC, JPEG, etc) to .png + normalizes name of images to Kebab Case
Category: Development
Language: python
Created by: lvcaspacifico Created At: Mar 10, 2024
Updated: Mar 10, 2024
Observations
Please pay attention to the comment section in the code, it contains instructions on how to run the script.
Script
"""
Media normalizer (recursivo)
----------------------------
Para cada arquivo na pasta apontada e em todas as subpastas:
- HEIC → convertido para PNG lossless, renomeado para kebab-case
- JPG/JPEG/BMP/
TIFF/WEBP/PNG → convertido para PNG lossless, renomeado para kebab-case
- MOV (ou
qualquer outro) → copiado com nome kebab-case, extensão mantida
Toda a saída vai para uma subpasta "converted/" dentro da pasta raiz,
espelhando a estrutura de subpastas original. Os originais nunca são tocados.
Colisões de nome são resolvidas automaticamente: foto.png → foto-2.png
Regra de renomeação: 'Hello World Image' / 'MyClientPhoto' → 'hello-world-image'
Requisitos:
pip install pillow pillow-heif
Uso:
python convert_heic.py # processa a pasta atual
python convert_heic.py "C:/caminho/pasta" # processa pasta específica
"""
import os
import re
import sys
import shutil
IMAGE_EXTENSIONS = {'.heic', '.jpg', '.jpeg', '.bmp', '.tiff', '.tif', '.webp', '.png'}
MAX_WIDTH = 2500
# ── Helpers ───────────────────────────────────────────────────────────────────
def normalize_name(stem: str) -> str:
stem = re.sub(r'(?<=[a-z0-9])(?=[A-Z])', ' ', stem) # split PascalCase
stem = stem.lower()
stem = re.sub(r'[\s_\-]+', '-', stem) # espaços → hífens
return stem.strip('-')
def unique_dst(path: str) -> str:
"""Se o caminho já existir, adiciona -2, -3, etc. até encontrar um livre."""
if not os.path.exists(path):
return path
base, ext = os.path.splitext(path)
counter = 2
while True:
candidate = f"{base}-{counter}{ext}"
if not os.path.exists(candidate):
return candidate
counter += 1
def collect_files(root_folder: str, out_dir: str):
"""
Percorre root_folder recursivamente (excluindo out_dir).
Retorna duas listas de tuplas (src_path, dst_path):
- images: arquivos de imagem a converter para PNG
- others: demais arquivos a copiar com renomeação
"""
images, others = [], []
for dirpath, dirnames, filenames in os.walk(root_folder):
# Evita entrar na pasta de saída
dirnames[:] = [
d for d in sorted(dirnames)
if os.path.abspath(os.path.join(dirpath, d)) != os.path.abspath(out_dir)
]
# Calcula o subcaminho relativo à pasta raiz
rel_dir = os.path.relpath(dirpath, root_folder)
dst_dir = os.path.join(out_dir, rel_dir) if rel_dir != '.' else out_dir
for f in sorted(filenames):
src = os.path.join(dirpath, f)
ext = os.path.splitext(f)[1].lower()
stem = os.path.splitext(f)[0]
if ext in IMAGE_EXTENSIONS:
dst = unique_dst(os.path.join(dst_dir, normalize_name(stem) + '.png'))
images.append((src, dst, dst_dir))
else:
dst = unique_dst(os.path.join(dst_dir, normalize_name(stem) + ext.lower()))
others.append((src, dst, dst_dir))
return images, others
# ── Conversão ─────────────────────────────────────────────────────────────────
def convert_to_png(src: str, dst: str) -> bool:
try:
from pillow_heif import register_heif_opener
from PIL import Image
register_heif_opener()
img = Image.open(src)
if img.mode not in ('RGB', 'RGBA', 'L', 'LA'):
img = img.convert('RGBA' if 'A' in img.getbands() else 'RGB')
if img.width > MAX_WIDTH:
new_height = round(img.height * (MAX_WIDTH / img.width))
img = img.resize((MAX_WIDTH, new_height), Image.LANCZOS)
print(f" redimensionado para {MAX_WIDTH}x{new_height}")
img.save(dst, format='PNG', optimize=False, compress_level=0)
return True
except Exception as e:
print(f" [erro] {e}")
return False
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
folder = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
folder = os.path.abspath(folder)
if not os.path.isdir(folder):
print(f"Erro: '{folder}' não é um diretório válido.")
sys.exit(1)
out_dir = os.path.join(folder, 'converted')
images, others = collect_files(folder, out_dir)
total = len(images) + len(others)
if total == 0:
print(f"Nenhum arquivo encontrado em: {folder}")
sys.exit(0)
os.makedirs(out_dir, exist_ok=True)
print(f"Origem: {folder}")
print(f"Saída: {out_dir}")
print(f"Encontrado(s): {len(images)} imagem(ns) para converter + {len(others)} arquivo(s) para renomear\n")
converted = renamed = failed = 0
# ── Imagens → PNG ─────────────────────────────────────────────────────────
if images:
print("── Imagens ──────────────────────────────────────────────")
for src, dst, dst_dir in images:
os.makedirs(dst_dir, exist_ok=True)
# Mostra caminho relativo para facilitar leitura no terminal
src_rel = os.path.relpath(src, folder)
dst_rel = os.path.relpath(dst, out_dir)
print(f" ... {src_rel} → {dst_rel}")
if convert_to_png(src, dst):
print(f" OK {dst_rel}")
converted += 1
else:
print(f" FAIL {src_rel}")
failed += 1
# ── Outros arquivos → copiar e renomear ───────────────────────────────────
if others:
print("\n── Outros arquivos (somente renomeação) ─────────────────")
for src, dst, dst_dir in others:
os.makedirs(dst_dir, exist_ok=True)
src_rel = os.path.relpath(src, folder)
dst_rel = os.path.relpath(dst, out_dir)
shutil.copy2(src, dst)
print(f" OK {src_rel} → {dst_rel}")
renamed += 1
print(f"""
Concluído. {converted} imagem(ns) convertida(s) para PNG
{renamed} arquivo(s) copiado(s) e renomeado(s)
{failed} falha(s)
""")
if __name__ == '__main__':
main()