Neue Sturktur

This commit is contained in:
2025-09-10 11:50:23 +02:00
parent 97ae257e44
commit 00f4337d59
5 changed files with 316 additions and 363 deletions

View File

@@ -1,37 +1,47 @@
# PPTX Image Compressor (CaesiumCLT only) # PPTX Image Compressor (CaesiumCLT only)
**Version 1.0.0** **Version 1.0.0**
Ein CLITool, das **PPTX-Dateien** entpackt, alle Bilder in `ppt/media` mit **CaesiumCLT** komprimiert und anschließend wieder zu einer PPTX packt. Dieses Paket enthält:
Es ersetzt nur dann Bilder, **wenn die komprimierte Version kleiner ist**, schreibt ein **CSV-Log** und zeigt eine **Fortschrittsanzeige** sowie eine **Summary inkl. Laufzeit** an.
--- ```
PPTX-Image-Compressor-1.0.0/
├─ README.md
├─ install_and_run.bat
├─ pptx_image_compress.py
├─ bin/
│ └─ PUT_caesiumclt_here.txt
└─ samples/
└─ README.txt
```
## Features ## Schnellstart (ohne Admin-Rechte)
- **Nur CaesiumCLT** als Bildkompressor (keine Pillow-Abhängigkeit). 1) Lege `caesiumclt.exe` in den Ordner `bin/` **oder** sorge dafür, dass es im `PATH` liegt.
- Unterstützte Bildtypen: **JPG/JPEG, PNG, WebP** (GIF wird übersprungen). 2) Doppelklicke `install_and_run.bat` **oder** rufe es in CMD/PowerShell auf, z.B.:
- **Overwrite-Policy:** `-O bigger` (nur überschreiben, wenn Ziel größer ist).
- **Multi-Threading**: Parallele Bildverarbeitung.
- **Log** (`.log` neben der Output-Datei) mit:
`image_name,size_before,size_after,saving,saving_percent`.
- **Sauberes Cleanup**: Keine Caesium-Tempdateien in der finalen PPTX.
--- ```bat
install_and_run.bat -i "C:\Slides\Deck.pptx" -t 8
```
## Systemvoraussetzungen Die Batch lädt bei Bedarf automatisch das **Windows Embeddable Python Package**, entpackt es lokal und führt das Tool aus.
- **Windows 10/11 (64-bit)**
- **Keine Admin-Rechte erforderlich**
- **CaesiumCLT** (portabler Download)
- **Python** (portabel, ohne Installation) **oder** fertige EXE
--- ## Was das Tool macht
- Entpackt die PPTX in einen TempOrdner
- Komprimiert **JPG/JPEG, PNG, WebP** mit **CaesiumCLT** (`-q 90`, `-O bigger`)
- Ersetzt Bilder nur, wenn die komprimierte Datei kleiner ist
- Schreibt ein CSVLog (`.log` neben der OutputPPTX)
- Baut eine neue PPTX und zeigt eine Summary (Name, Größe vorher/nachher, Ersparnis %, Zeit)
- Räumt alle temporären Dateien auf (keine CaesiumTempfiles in der finalen PPTX)
## Installation & Setup ## Hinweise
- **GIF** wird übersprungen (keine Rekodierung).
- `-t` steuert die Parallelität (PythonThreads); intern wird `caesiumclt --threads 1` gesetzt, sobald `-t > 1`, um Oversubscription zu vermeiden.
### 1) CaesiumCLT installieren ## Manuelle Nutzung des .py (falls Python vorhanden)
1. Lade **CaesiumCLT** von GitHub herunter: ```bat
[https://github.com/Lymphatus/caesium-clt/releases](https://github.com/Lymphatus/caesium-clt) python pptx_image_compress.py -i "C:\Pfad\input.pptx" -t 8
2. Entpacke `caesiumclt.exe` in einen Ordner, z.B. `C:\Tools\caesiumclt`. ```
3. Füge den Ordner temporär zum PATH hinzu (optional):
```bat ## Quellen & Tools
set PATH=C:\Tools\caesiumclt;%PATH% - CaesiumCLT Projekt/Downloads: https://github.com/Lymphatus/caesium-clt
``` - Windows Embeddable Python Package Doku/Downloads: https://docs.python.org/3/using/windows.html

View File

@@ -0,0 +1 @@
Place caesiumclt.exe in this folder or ensure it is available on PATH.

View File

@@ -1,3 +1,4 @@
@echo off @echo off
setlocal EnableExtensions EnableDelayedExpansion setlocal EnableExtensions EnableDelayedExpansion
@@ -29,7 +30,7 @@ if exist "%SELF_DIR%%CAE_EXE%" (
where /q %CAE_EXE% where /q %CAE_EXE%
if errorlevel 1 ( if errorlevel 1 (
echo [ERROR] ^> 'caesiumclt.exe' nicht gefunden. echo [ERROR] ^> 'caesiumclt.exe' nicht gefunden.
echo Lege 'caesiumclt.exe' neben diese BAT ^(empfohlen^) echo Lege 'caesiumclt.exe' neben diese BAT (empfohlen)
echo oder sorge dafuer, dass es im PATH liegt. echo oder sorge dafuer, dass es im PATH liegt.
exit /b 2 exit /b 2
) )
@@ -89,7 +90,7 @@ if not defined PY_CMD (
exit /b 5 exit /b 5
) )
rem ---- Optional: 'import site' im Embeddable aktivieren (freundlich fuer spaetere Erweiterungen) ---- rem ---- Optional: 'import site' im Embeddable aktivieren ----
if exist "%PY_DIR%" ( if exist "%PY_DIR%" (
for /f "delims=" %%F in ('dir /b "%PY_DIR%\python3*.pth" 2^>nul') do ( for /f "delims=" %%F in ('dir /b "%PY_DIR%\python3*.pth" 2^>nul') do (
set "PTH_FILE=%PY_DIR%\%%F" set "PTH_FILE=%PY_DIR%\%%F"
@@ -111,7 +112,6 @@ echo.
echo [%APP_NAME%] Starte ... echo [%APP_NAME%] Starte ...
echo Command: "%PY_CMD%" "%SCRIPT%" %* echo Command: "%PY_CMD%" "%SCRIPT%" %*
echo. echo.
"%PY_CMD%" "%SCRIPT%" %* "%PY_CMD%" "%SCRIPT%" %*
set "RC=%ERRORLEVEL%" set "RC=%ERRORLEVEL%"

View File

@@ -1,333 +1,274 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
PPTX Grafik-Komprimier-Tool (nur CaesiumCLT, Multi-Thread, sauberes Cleanup) PPTX Grafik-Komprimier-Tool (nur CaesiumCLT, Multi-Thread, sauberes Cleanup)
Version: 1.0.0 Version: 1.0.0
Highlights: Highlights:
- Caesium-Scratch außerhalb des PPTX-Arbeitsverzeichnisses -> keine Tempfiles in finaler PPTX - Caesium-Scratch außerhalb des PPTX-Arbeitsverzeichnisses -> keine Tempfiles in finaler PPTX
- Safety-Cleanup: entfernt 'caesium*' Ordner und '*.tmp' in ppt/media, bevor gezippt wird - Safety-Cleanup: entfernt 'caesium*' Ordner und '*.tmp' in ppt/media, bevor gezippt wird
- Overwrite Policy: -O bigger - Overwrite Policy: -O bigger
- Log: image_name,size_before,size_after,saving,saving_percent - Log: image_name,size_before,size_after,saving,saving_percent
- Summary inkl. Zeit benötigt - Summary inkl. Zeit benötigt
Benutzung: Benutzung:
python pptx_image_compress.py -i input.pptx [-o output.pptx] [-t THREADS] [--version] python pptx_image_compress.py -i input.pptx [-o output.pptx] [-t THREADS] [--version]
""" """
import argparse import argparse
import os import os
import sys import sys
import zipfile import zipfile
import tempfile import tempfile
import shutil import shutil
import subprocess import subprocess
import time import time
from pathlib import Path from pathlib import Path
from datetime import timedelta from datetime import timedelta
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock from threading import Lock
# -------------------- Version -------------------- __version__ = "1.0.0"
__version__ = "1.0.0"
ALLOWED_EXT = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
# -------------------- Konfiguration -------------------- CAESIUM_QUALITY = 90
ALLOWED_EXT = {".jpg", ".jpeg", ".png", ".webp", ".gif"} # GIF wird übersprungen PROGRESS_BAR_LEN = 40
CAESIUM_QUALITY = 90 # -q 90 TEMP_PREFIX = "pptx_compress_"
PROGRESS_BAR_LEN = 40
TEMP_PREFIX = "pptx_compress_" def human_mb(nbytes: int) -> float:
return round(nbytes / (1024 * 1024), 2)
# -------------------- Utilities -------------------- def ensure_clean_file(path: Path):
def human_mb(nbytes: int) -> float: if path.exists():
return round(nbytes / (1024 * 1024), 2) try:
if path.is_file():
path.unlink()
def ensure_clean_file(path: Path): else:
if path.exists(): shutil.rmtree(path, ignore_errors=True)
try: except Exception:
if path.is_file(): pass
path.unlink()
else: def cleanup_old_temps():
shutil.rmtree(path, ignore_errors=True) tmp_root = Path(tempfile.gettempdir())
except Exception: for p in tmp_root.glob(f"{TEMP_PREFIX}*"):
pass try:
if p.is_dir():
shutil.rmtree(p, ignore_errors=True)
def cleanup_old_temps(): else:
tmp_root = Path(tempfile.gettempdir()) p.unlink(missing_ok=True)
for p in tmp_root.glob(f"{TEMP_PREFIX}*"): except Exception:
try: pass
if p.is_dir():
shutil.rmtree(p, ignore_errors=True) def print_progress(i: int, total: int):
else: if total <= 0:
p.unlink(missing_ok=True) return
except Exception: done = int(PROGRESS_BAR_LEN * i / total)
pass bar = "" * done + "-" * (PROGRESS_BAR_LEN - done)
pct = int(i * 100 / total)
print(f"
def print_progress(i: int, total: int): Bilder: |{bar}| {i}/{total} ({pct}%)", end="", flush=True)
if total <= 0:
return def zip_dir_to_pptx(src_dir: Path, out_pptx: Path):
done = int(PROGRESS_BAR_LEN * i / total) with zipfile.ZipFile(out_pptx, "w", compression=zipfile.ZIP_DEFLATED) as z:
bar = "" * done + "-" * (PROGRESS_BAR_LEN - done) for root, _, files in os.walk(src_dir):
pct = int(i * 100 / total) for f in files:
print(f"\rBilder: |{bar}| {i}/{total} ({pct}%)", end="", flush=True) full = Path(root) / f
rel = full.relative_to(src_dir)
z.write(full, arcname=str(rel))
def zip_dir_to_pptx(src_dir: Path, out_pptx: Path):
with zipfile.ZipFile(out_pptx, "w", compression=zipfile.ZIP_DEFLATED) as z: def which(cmd: str):
for root, _, files in os.walk(src_dir): return shutil.which(cmd)
for f in files:
full = Path(root) / f def compress_with_caesium(original: Path, out_dir: Path, caesium_threads: int | None) -> Path | None:
rel = full.relative_to(src_dir) exe = which("caesiumclt")
z.write(full, arcname=str(rel)) if not exe:
raise RuntimeError("'caesiumclt' wurde nicht gefunden. Bitte CaesiumCLT installieren und in PATH verfügbar machen.")
out_dir.mkdir(parents=True, exist_ok=True)
def which(cmd: str) -> str | None: ext = original.suffix.lower()
return shutil.which(cmd) if ext not in {".jpg", ".jpeg", ".png", ".webp"}:
return None
cmd = [exe, "-q", str(CAESIUM_QUALITY), "-O", "bigger", "-o", str(out_dir)]
def compress_with_caesium(original: Path, out_dir: Path, caesium_threads: int | None) -> Path | None: if caesium_threads is not None:
""" cmd += ["--threads", str(caesium_threads)]
Ruft caesiumclt auf, um eine komprimierte Version zu erzeugen. cmd += [str(original)]
Output wird ins out_dir geschrieben (gleicher Filename). try:
Gibt Pfad zur erzeugten Datei zurück oder None bei Fehler. r = subprocess.run(cmd, capture_output=True, text=True)
""" if r.returncode != 0:
exe = which("caesiumclt") sys.stderr.write(f"
if not exe: [caesiumclt] Fehler bei {original.name}:
raise RuntimeError( {r.stderr}
"'caesiumclt' wurde nicht gefunden. Bitte CaesiumCLT installieren und in PATH verfügbar machen." ")
) return None
out_file = out_dir / original.name
out_dir.mkdir(parents=True, exist_ok=True) return out_file if out_file.exists() else None
except Exception as ex:
# Nur Formate an Caesium geben, die es unterstützt: JPG/JPEG, PNG, WEBP sys.stderr.write(f"
ext = original.suffix.lower() [caesiumclt] Ausnahme bei {original.name}: {ex}
if ext not in {".jpg", ".jpeg", ".png", ".webp"}: ")
return None # GIF & andere werden übersprungen return None
cmd = [ def format_duration(seconds: float) -> str:
exe, total_ms = int(round(seconds * 1000))
"-q", str(CAESIUM_QUALITY), td = timedelta(milliseconds=total_ms)
"-O", "bigger", # <<< nur überschreiben, wenn Ziel größer ist base = str(td)
"-o", str(out_dir), if "." in base:
] hms, frac = base.split(".", 1)
if caesium_threads is not None: return f"{hms}.{frac[:2]}"
cmd += ["--threads", str(caesium_threads)] return base
cmd += [str(original)]
def main():
try: start_time = time.perf_counter()
r = subprocess.run(cmd, capture_output=True, text=True)
if r.returncode != 0: parser = argparse.ArgumentParser(description="PPTX Grafik-Komprimier-Tool (nur CaesiumCLT, Multi-Thread, sauberes Cleanup)", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
sys.stderr.write(f"\n[caesiumclt] Fehler bei {original.name}:\n{r.stderr}\n") parser.add_argument("-i", "--input", help="Input-PPTX", required=False)
return None parser.add_argument("-o", "--output", help="Output-PPTX", required=False)
parser.add_argument("-t", "--threads", type=int, default=min(32, os.cpu_count() or 4), help="Anzahl paralleler Threads für die Bildverarbeitung")
out_file = out_dir / original.name parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
return out_file if out_file.exists() else None
except Exception as ex: args = parser.parse_args()
sys.stderr.write(f"\n[caesiumclt] Ausnahme bei {original.name}: {ex}\n")
return None if not args.input:
parser.print_help()
sys.exit(1)
def format_duration(seconds: float) -> str:
total_ms = int(round(seconds * 1000)) input_pptx = Path(args.input).resolve()
td = timedelta(milliseconds=total_ms) if not input_pptx.exists() or input_pptx.suffix.lower() != ".pptx":
base = str(td) print("❌ Eingabedatei existiert nicht oder ist keine .pptx")
if "." in base: sys.exit(2)
hms, frac = base.split(".", 1)
return f"{hms}.{frac[:2]}" output_pptx = Path(args.output).resolve() if args.output else input_pptx.with_name(f"{input_pptx.stem}_compressed.pptx")
return base
cleanup_old_temps()
ensure_clean_file(output_pptx)
def main():
start_time = time.perf_counter() work_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "work_"))
scratch_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "scratch_"))
parser = argparse.ArgumentParser(
description="PPTX Grafik-Komprimier-Tool (nur CaesiumCLT, Multi-Thread, sauberes Cleanup)", log_file = output_pptx.with_suffix(".log")
formatter_class=argparse.ArgumentDefaultsHelpFormatter, ensure_clean_file(log_file)
) log_lines = ["image_name,size_before,size_after,saving,saving_percent
parser.add_argument("-i", "--input", help="Input-PPTX", required=False) "]
parser.add_argument("-o", "--output", help="Output-PPTX", required=False)
parser.add_argument( size_before = input_pptx.stat().st_size
"-t", "--threads",
type=int, try:
default=min(32, os.cpu_count() or 4), with zipfile.ZipFile(input_pptx, "r") as z:
help="Anzahl paralleler Threads für die Bildverarbeitung" z.extractall(work_dir)
)
parser.add_argument( media_dir = work_dir / "ppt" / "media"
"--version", images = []
action="version", if media_dir.exists():
version=f"%(prog)s {__version__}" for f in sorted(media_dir.iterdir()):
) if f.is_file() and f.suffix.lower() in ALLOWED_EXT:
images.append(f)
args = parser.parse_args()
total = len(images)
if not args.input: print(f"🔧 Finde Bilder in {media_dir} ... {total} Kandidaten")
parser.print_help() print_progress(0, total)
sys.exit(1)
if not which("caesiumclt"):
input_pptx = Path(args.input).resolve() print("
if not input_pptx.exists() or input_pptx.suffix.lower() != ".pptx": 'caesiumclt' nicht gefunden. Bitte installieren und in PATH verfügbar machen.")
print("❌ Eingabedatei existiert nicht oder ist keine .pptx") sys.exit(3)
sys.exit(2)
caesium_threads = 1 if args.threads and args.threads > 1 else None
if args.output:
output_pptx = Path(args.output).resolve() lock = Lock()
else: done_count = 0
output_pptx = input_pptx.with_name(f"{input_pptx.stem}_compressed.pptx")
def worker(idx: int, img_path: Path):
# Vorherige Temp-Files & existierendes Output löschen nonlocal done_count
cleanup_old_temps() ext = img_path.suffix.lower()
ensure_clean_file(output_pptx) orig_size = img_path.stat().st_size
if ext == ".gif":
# --- Zwei getrennte Temp-Verzeichnisse --- with lock:
work_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "work_")) # entpackte PPTX done_count += 1
scratch_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "scratch_")) # Caesium-Ausgaben (außerhalb!) log_lines.append(f"{img_path.name},{orig_size},{orig_size},0,0.0
")
# Logdatei neben Output print_progress(done_count, total)
log_file = output_pptx.with_suffix(".log") return
ensure_clean_file(log_file) chosen_size = orig_size
log_lines = ["image_name,size_before,size_after,saving,saving_percent\n"] try:
out_sub = scratch_dir / f"img_{idx:06d}"
size_before = input_pptx.stat().st_size caesium_out = compress_with_caesium(img_path, out_sub, caesium_threads)
if caesium_out and caesium_out.exists():
try: s = caesium_out.stat().st_size
# Entpacken if s < orig_size:
with zipfile.ZipFile(input_pptx, "r") as z: tmp_target = img_path.with_suffix(img_path.suffix + ".tmp")
z.extractall(work_dir) shutil.copy2(caesium_out, tmp_target)
tmp_target.replace(img_path)
media_dir = work_dir / "ppt" / "media" chosen_size = s
images = [] except Exception:
if media_dir.exists(): chosen_size = orig_size
for f in sorted(media_dir.iterdir()): finally:
if f.is_file() and f.suffix.lower() in ALLOWED_EXT: saving = orig_size - chosen_size
images.append(f) saving_percent = round((saving / orig_size) * 100, 2) if orig_size > 0 else 0.0
with lock:
total = len(images) log_lines.append(f"{img_path.name},{orig_size},{chosen_size},{saving},{saving_percent}
print(f"🔧 Finde Bilder in {media_dir} ... {total} Kandidaten") ")
print_progress(0, total) done_count += 1
print_progress(done_count, total)
# Vorab prüfen, ob caesiumclt verfügbar ist
if not which("caesiumclt"): if total > 0:
print("\n'caesiumclt' nicht gefunden. Bitte installieren und in PATH verfügbar machen.") with ThreadPoolExecutor(max_workers=max(1, args.threads)) as ex:
sys.exit(3) futures = [ex.submit(worker, i, p) for i, p in enumerate(images, start=1)]
for _ in as_completed(futures):
# Oversubscription vermeiden: viele Python-Threads -> caesium intern 1 Thread pass
caesium_threads = 1 if args.threads and args.threads > 1 else None
print()
# Thread-sichere Fortschritts- & Log-Verwaltung
lock = Lock() for p in work_dir.rglob("*"):
done_count = 0 try:
if p.is_dir() and p.name.lower().startswith("caesium"):
def worker(idx: int, img_path: Path): shutil.rmtree(p, ignore_errors=True)
nonlocal done_count except Exception:
ext = img_path.suffix.lower() pass
orig_size = img_path.stat().st_size
media_dir = work_dir / "ppt" / "media"
# GIF überspringen if media_dir.exists():
if ext == ".gif": for f in media_dir.iterdir():
with lock: if f.is_file() and f.suffix.lower() == ".tmp":
done_count += 1 try:
log_lines.append(f"{img_path.name},{orig_size},{orig_size},0,0.0\n") f.unlink(missing_ok=True)
print_progress(done_count, total) except Exception:
return pass
chosen_size = orig_size zip_dir_to_pptx(work_dir, output_pptx)
try: size_after = output_pptx.stat().st_size
# Eigener Output-Unterordner pro Bild, um Kollisionen zu vermeiden
out_sub = scratch_dir / f"img_{idx:06d}" try:
caesium_out = compress_with_caesium(img_path, out_sub, caesium_threads) with open(log_file, "w", encoding="utf-8") as f:
f.writelines(log_lines)
if caesium_out and caesium_out.exists(): except Exception:
s = caesium_out.stat().st_size pass
if s < orig_size:
# kleineren ersetzen (atomar) savings_pct = 0.0
tmp_target = img_path.with_suffix(img_path.suffix + ".tmp") if size_before > 0:
shutil.copy2(caesium_out, tmp_target) savings_pct = round(100.0 * (size_before - size_after) / size_before, 2)
tmp_target.replace(img_path)
chosen_size = s elapsed = time.perf_counter() - start_time
except Exception: print("
chosen_size = orig_size # Original beibehalten Fertig!")
print("Summary")
finally: print("-------")
saving = orig_size - chosen_size print(f"Version: {__version__}")
saving_percent = round((saving / orig_size) * 100, 2) if orig_size > 0 else 0.0 print(f"Name: {output_pptx.name}")
with lock: print(f"Datei-Größe vorher: {human_mb(size_before)} MB")
log_lines.append(f"{img_path.name},{orig_size},{chosen_size},{saving},{saving_percent}\n") print(f"Datei-Größe nachher: {human_mb(size_after)} MB")
done_count += 1 print(f"Ersparnis: {savings_pct}%")
print_progress(done_count, total) print(f"Zeit benötigt: {format_duration(elapsed)}")
print(f"Log-Datei: {log_file}")
# Parallel ausführen
if total > 0: finally:
with ThreadPoolExecutor(max_workers=max(1, args.threads)) as ex: try:
futures = [ex.submit(worker, i, p) for i, p in enumerate(images, start=1)] shutil.rmtree(work_dir, ignore_errors=True)
for _ in as_completed(futures): except Exception:
pass # Fortschritt wird im Worker gezeichnet pass
try:
print() # newline nach Progressbar shutil.rmtree(scratch_dir, ignore_errors=True)
except Exception:
# --- Safety-Cleanup innerhalb des Arbeitsverzeichnisses --- pass
# 1) Entferne evtl. vorhandene caesium*-Ordner (aus alten Runs) cleanup_old_temps()
for p in work_dir.rglob("*"):
try: if __name__ == "__main__":
if p.is_dir() and p.name.lower().startswith("caesium"):
shutil.rmtree(p, ignore_errors=True)
except Exception:
pass
# 2) Lösche eventuelle .tmp-Dateien in ppt/media
media_dir = work_dir / "ppt" / "media"
if media_dir.exists():
for f in media_dir.iterdir():
if f.is_file() and f.suffix.lower() == ".tmp":
try:
f.unlink(missing_ok=True)
except Exception:
pass
# Neue PPTX bauen (nur work_dir -> scratch_dir liegt außerhalb und ist damit sicher ausgeschlossen)
zip_dir_to_pptx(work_dir, output_pptx)
size_after = output_pptx.stat().st_size
# Log schreiben
try:
with open(log_file, "w", encoding="utf-8") as f:
f.writelines(log_lines)
except Exception:
pass
# Summary
savings_pct = 0.0
if size_before > 0:
savings_pct = round(100.0 * (size_before - size_after) / size_before, 2)
elapsed = time.perf_counter() - start_time
print("\n✅ Fertig!")
print("Summary")
print("-------")
print(f"Version: {__version__}")
print(f"Name: {output_pptx.name}")
print(f"Datei-Größe vorher: {human_mb(size_before)} MB")
print(f"Datei-Größe nachher: {human_mb(size_after)} MB")
print(f"Ersparnis: {savings_pct}%")
print(f"Zeit benötigt: {format_duration(elapsed)}")
print(f"Log-Datei: {log_file}")
finally:
# Aufräumen ALLER temporären Dateien/Ordner
try:
shutil.rmtree(work_dir, ignore_errors=True)
except Exception:
pass
try:
shutil.rmtree(scratch_dir, ignore_errors=True)
except Exception:
pass
# Zusätzlich: ältere Reste entfernen
cleanup_old_temps()
if __name__ == "__main__":
main()

1
samples/README.txt Normal file
View File

@@ -0,0 +1 @@
Place your PPTX files here for testing, or use -i with a full path.