Исправление обработки путей и хейшей после конвертации

This commit is contained in:
2025-05-14 02:24:49 +03:00
parent eda9e14058
commit 500cf98994

View File

@@ -13,7 +13,6 @@ from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
ImageFile.LOAD_TRUNCATED_IMAGES = True ImageFile.LOAD_TRUNCATED_IMAGES = True
warnings.filterwarnings("ignore", category=UserWarning, module="PIL") warnings.filterwarnings("ignore", category=UserWarning, module="PIL")
logging.basicConfig( logging.basicConfig(
@@ -71,47 +70,48 @@ def inject_exif(jpeg_path, exif_bytes):
logging.error(f"Ошибка при вставке EXIF в {jpeg_path}: {e}") logging.error(f"Ошибка при вставке EXIF в {jpeg_path}: {e}")
def convert_png_to_jpeg(path: str) -> bool: def convert_png_to_jpeg(path: Path) -> Path | None:
try: try:
temp_path = path.with_suffix(".jpg")
with Image.open(path) as img: with Image.open(path) as img:
temp_path = path.with_suffix(".jpg")
img.convert("RGB").save( img.convert("RGB").save(
temp_path, "JPEG", quality=85, optimize=True temp_path, "JPEG", quality=85, optimize=True
) )
if temp_path.stat().st_size < path.stat().st_size:
new_size = temp_path.stat().st_size path.unlink()
if new_size < path.stat().st_size: return temp_path
temp_path.replace(path) else:
return True temp_path.unlink()
except Exception as e: except Exception as e:
logging.error(f"Ошибка при конвертации PNG в JPEG для {path}: {e}") logging.error(f"Ошибка при конвертации PNG в JPEG для {path}: {e}")
return False return None
def compress_with_external(path: str, ext: str) -> bool: def compress_with_external(path: str, ext: str) -> tuple[bool, Path]:
path = Path(path) path = Path(path)
original_size = path.stat().st_size original_size = path.stat().st_size
tmp_path = path.with_name(path.name + ".compressed") tmp_path = path.with_name(path.stem + ".compressed" + path.suffix)
target_size = TARGET_SIZE_MB target_size = TARGET_SIZE_MB
try: try:
if ext == ".png": if ext == ".png":
if not convert_png_to_jpeg(path): new_path = convert_png_to_jpeg(path)
return False if not new_path:
return False, path
path = new_path
ext = ".jpg" ext = ".jpg"
if ext in [".jpg", ".jpeg"]: if ext in [".jpg", ".jpeg"]:
exif_data = extract_exif(path) exif_data = extract_exif(path)
tool = get_tool_path("cjpeg-static.exe") tool = get_tool_path("cjpeg-static.exe")
quality = 85 quality = 85
while True: while True:
subprocess.run( subprocess.run(
[ [
tool, tool,
f"-quality", "-quality",
str(quality), str(quality),
f"-outfile", "-outfile",
str(tmp_path), str(tmp_path),
str(path), str(path),
], ],
@@ -122,7 +122,6 @@ def compress_with_external(path: str, ext: str) -> bool:
if os.path.getsize(tmp_path) <= target_size or quality < 50: if os.path.getsize(tmp_path) <= target_size or quality < 50:
break break
quality -= 5 quality -= 5
if tmp_path.exists() and exif_data: if tmp_path.exists() and exif_data:
inject_exif(tmp_path, exif_data) inject_exif(tmp_path, exif_data)
@@ -151,31 +150,33 @@ def compress_with_external(path: str, ext: str) -> bool:
break break
quality -= 5 quality -= 5
else: else:
return False return False, path
except FileNotFoundError: except FileNotFoundError:
return None return None, path
except Exception as e:
logging.error(f"Ошибка внешнего сжатия {path}: {e}")
return False, path
if tmp_path.exists(): if tmp_path.exists():
new_size = os.path.getsize(tmp_path) new_size = tmp_path.stat().st_size
if new_size < original_size: if new_size < original_size:
tmp_path.replace(path) tmp_path.replace(path)
return True return True, path
else: else:
tmp_path.unlink() tmp_path.unlink()
return False return False, path
def compress_with_pillow(path: str) -> bool: def compress_with_pillow(path: str) -> tuple[bool, Path]:
original_size = os.path.getsize(path) path = Path(path)
target_size = TARGET_SIZE_MB original_size = path.stat().st_size
temp_path = Path(path).with_suffix(".pillowtmp") temp_path = path.with_name(path.stem + ".pillowtmp" + path.suffix)
try: try:
with Image.open(path) as img: with Image.open(path) as img:
img_format = img.format img_format = img.format
exif = img.info.get("exif", None) exif = img.info.get("exif", None)
quality = 85 quality = 85
while quality >= 50: while quality >= 50:
img.save( img.save(
temp_path, temp_path,
@@ -184,73 +185,72 @@ def compress_with_pillow(path: str) -> bool:
quality=quality, quality=quality,
exif=exif, exif=exif,
) )
if temp_path.stat().st_size <= target_size: if temp_path.stat().st_size <= TARGET_SIZE_MB:
break break
quality -= 5 quality -= 5
if temp_path.exists() and temp_path.stat().st_size < original_size: if temp_path.exists() and temp_path.stat().st_size < original_size:
temp_path.replace(path) temp_path.replace(path)
return True return True, path
elif temp_path.exists(): elif temp_path.exists():
temp_path.unlink() temp_path.unlink()
return False return False, path
except Exception as e: except Exception as e:
logging.error(f"Ошибка Pillow для {path}: {e}") logging.error(f"Ошибка Pillow для {path}: {e}")
return False return False, path
def compress_image(path: str, fallback_to_pillow: bool = False): def compress_image(path: str, fallback_to_pillow: bool = False):
global processed_count, skipped_count, total_saved_bytes global processed_count, skipped_count, total_saved_bytes
try: try:
original_size = os.path.getsize(path) path = Path(path)
original_size = path.stat().st_size
if original_size < MIN_SIZE: if original_size < MIN_SIZE:
skipped_count += 1 skipped_count += 1
msg = f"Пропущено (уже малый): {path} ({original_size / 1024:.1f} KB)" logging.info(
logging.info(msg) f"Пропущено (уже малый): {path} ({original_size / 1024:.1f} KB)"
)
return return
h = file_hash(path) h = file_hash(path)
filename = path.name
with db_lock: with db_lock:
cursor.execute("SELECT 1 FROM processed WHERE hash = ?", (h,)) cursor.execute("SELECT 1 FROM processed WHERE hash = ?", (h,))
if cursor.fetchone(): if cursor.fetchone():
skipped_count += 1 skipped_count += 1
msg = f"Пропущено (уже сжато): {path} ({original_size / 1024:.1f} KB)" logging.info(
logging.info(msg) f"Пропущено (уже сжато): {path} ({original_size / 1024:.1f} KB)"
)
return return
ext = Path(path).suffix.lower() ext = path.suffix.lower()
result = compress_with_external(path, ext) result, path = compress_with_external(path, ext)
if result is None and fallback_to_pillow: if result is None and fallback_to_pillow:
result = compress_with_pillow(path) result, path = compress_with_pillow(path)
new_size = os.path.getsize(path) new_size = path.stat().st_size
if result and new_size < original_size: if result and new_size < original_size:
saved_bytes = original_size - new_size saved_bytes = original_size - new_size
total_saved_bytes += saved_bytes total_saved_bytes += saved_bytes
percent = (1 - new_size / original_size) * 100
saved_percent = (1 - new_size / original_size) * 100 logging.info(
msg = ( f"Сжато: {path} ({original_size / 1024:.1f} KB -> {new_size / 1024:.1f} KB, сохранено {percent:.2f}%)"
f"Сжато: {path} "
f"({original_size / 1024:.1f} KB -> {new_size / 1024:.1f} KB, "
f"сохранено {saved_percent:.2f}%)"
) )
logging.info(msg)
else: else:
msg = f"Пропущено (не меньше): {path} ({original_size / 1024:.1f} KB)" logging.info(
logging.info(msg) f"Пропущено (не меньше): {path} ({original_size / 1024:.1f} KB)"
)
h = file_hash(path)
with db_lock: with db_lock:
cursor.execute( cursor.execute(
"INSERT INTO processed(hash, filename) VALUES(?, ?)", "INSERT INTO processed(hash, filename) VALUES(?, ?)",
(h, filename), (h, path.name),
) )
conn.commit() conn.commit()
processed_count += 1
processed_count += 1
except Exception as e: except Exception as e:
logging.error(f"Ошибка обработки {path}: {e}") logging.error(f"Ошибка обработки {path}: {e}")
@@ -286,10 +286,11 @@ def main():
print("Проверка утилит...") print("Проверка утилит...")
required_tools = ["cjpeg-static.exe", "cwebp.exe"] required_tools = ["cjpeg-static.exe", "cwebp.exe"]
missing = [] missing = [
for tool in required_tools: tool
if not os.path.exists(get_tool_path(tool)): for tool in required_tools
missing.append(tool) if not os.path.exists(get_tool_path(tool))
]
fallback = False fallback = False
if missing: if missing: