Compare commits
2 Commits
698aac0aba
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c69ec1eecb | |||
| 252b2c2cd5 |
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
# PPTX Image Compressor (CaesiumCLT only)
|
# PPTX Image Compressor (CaesiumCLT only)
|
||||||
**Version 1.1.4**
|
**Version 1.1.6**
|
||||||
|
|
||||||
Dieses Paket enthält:
|
Dieses Paket enthält:
|
||||||
|
|
||||||
@@ -11,8 +11,6 @@ PPTX-Image-Compressor/
|
|||||||
├─ pptx_image_compress.py
|
├─ pptx_image_compress.py
|
||||||
├─ bin/
|
├─ bin/
|
||||||
│ └─ caesiumclt.exe
|
│ └─ caesiumclt.exe
|
||||||
└─ samples/
|
|
||||||
└─ README.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Schnellstart (ohne Admin-Rechte)
|
## Schnellstart (ohne Admin-Rechte)
|
||||||
@@ -34,6 +32,7 @@ Die Batch lädt bei Bedarf automatisch das **Windows Embeddable Python Package**
|
|||||||
- Entpackt die PPTX in einen Temp‑Ordner
|
- Entpackt die PPTX in einen Temp‑Ordner
|
||||||
- Komprimiert **JPG/JPEG, PNG, WebP, GIF** mit **CaesiumCLT** (Default `-q 90`, `-O bigger`)
|
- Komprimiert **JPG/JPEG, PNG, WebP, GIF** mit **CaesiumCLT** (Default `-q 90`, `-O bigger`)
|
||||||
- Ersetzt Bilder nur, wenn die komprimierte Datei kleiner ist
|
- Ersetzt Bilder nur, wenn die komprimierte Datei kleiner ist
|
||||||
|
- Ersetzt Bilder nur, wenn sei mindestens 2% kleiner sind (verhindert *doppelte Komprimierung*)
|
||||||
- Schreibt ein CSV‑Log (`.log` neben der Output‑PPTX)
|
- Schreibt ein CSV‑Log (`.log` neben der Output‑PPTX)
|
||||||
- Baut eine neue PPTX und zeigt eine Summary (Name, Größe vorher/nachher, Ersparnis %, Zeit)
|
- Baut eine neue PPTX und zeigt eine Summary (Name, Größe vorher/nachher, Ersparnis %, Zeit)
|
||||||
- Räumt alle temporären Dateien auf (keine Caesium‑Tempfiles in der finalen PPTX)
|
- Räumt alle temporären Dateien auf (keine Caesium‑Tempfiles in der finalen PPTX)
|
||||||
@@ -41,6 +40,7 @@ Die Batch lädt bei Bedarf automatisch das **Windows Embeddable Python Package**
|
|||||||
## Hinweise
|
## Hinweise
|
||||||
- `-t` steuert die Parallelität der Python‑Threads; intern wird `caesiumclt --threads 1` gesetzt, sobald `-t > 1`, um Oversubscription zu vermeiden. Default ist 16
|
- `-t` steuert die Parallelität der Python‑Threads; intern wird `caesiumclt --threads 1` gesetzt, sobald `-t > 1`, um Oversubscription zu vermeiden. Default ist 16
|
||||||
- `-q` steuert das Qualitätslevel; intern wird `caesiumclt -q` mit diesem Wert von `0..100` benutzt, Default ist 90
|
- `-q` steuert das Qualitätslevel; intern wird `caesiumclt -q` mit diesem Wert von `0..100` benutzt, Default ist 90
|
||||||
|
- `--min-savings` steuert das Mindestmass an Komprimierung zur Verhinderung von doppelter Komprimierunt, Default ist 2%
|
||||||
- Die Batch **verwendet bevorzugt das Embeddable Python** neben der BAT; ansonsten sucht sie echte `python.exe`/`py.exe` im PATH, **ignoriert** aber die Microsoft‑Store‑Alias‑Pfade (`WindowsApps`).
|
- Die Batch **verwendet bevorzugt das Embeddable Python** neben der BAT; ansonsten sucht sie echte `python.exe`/`py.exe` im PATH, **ignoriert** aber die Microsoft‑Store‑Alias‑Pfade (`WindowsApps`).
|
||||||
|
|
||||||
## Manuelle Nutzung des .py (falls Python vorhanden)
|
## Manuelle Nutzung des .py (falls Python vorhanden)
|
||||||
|
|||||||
@@ -116,10 +116,16 @@ def print_progress(i: int, total: int):
|
|||||||
print(f"\rBilder: |{bar}| {i}/{total} ({pct}%)", end="", flush=True)
|
print(f"\rBilder: |{bar}| {i}/{total} ({pct}%)", end="", flush=True)
|
||||||
|
|
||||||
def zip_dir_to_pptx(src_dir: Path, out_pptx: Path):
|
def zip_dir_to_pptx(src_dir: Path, out_pptx: Path):
|
||||||
with zipfile.ZipFile(out_pptx, "w", compression=zipfile.ZIP_DEFLATED) as z:
|
all_files: list[Path] = []
|
||||||
for root, _, files in os.walk(src_dir):
|
for root, _, files in os.walk(src_dir):
|
||||||
for f in files:
|
for f in files:
|
||||||
full = Path(root) / f
|
all_files.append(Path(root) / f)
|
||||||
|
|
||||||
|
content_types = [f for f in all_files if f.name == "[Content_Types].xml"]
|
||||||
|
rest = [f for f in all_files if f.name != "[Content_Types].xml"]
|
||||||
|
|
||||||
|
with zipfile.ZipFile(out_pptx, "w", compression=zipfile.ZIP_DEFLATED) as z:
|
||||||
|
for full in content_types + rest:
|
||||||
rel = full.relative_to(src_dir)
|
rel = full.relative_to(src_dir)
|
||||||
z.write(full, arcname=str(rel))
|
z.write(full, arcname=str(rel))
|
||||||
|
|
||||||
@@ -132,7 +138,7 @@ def compress_with_caesium(original: Path, out_dir: Path, caesium_threads: int |
|
|||||||
raise RuntimeError("[ERROR] 'caesiumclt' wurde nicht gefunden. Bitte CaesiumCLT installieren und in PATH verfügbar machen.")
|
raise RuntimeError("[ERROR] 'caesiumclt' wurde nicht gefunden. Bitte CaesiumCLT installieren und in PATH verfügbar machen.")
|
||||||
out_dir.mkdir(parents=True, exist_ok=True)
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
ext = original.suffix.lower()
|
ext = original.suffix.lower()
|
||||||
if ext not in {".jpg", ".jpeg", ".png", ".webp", ".gif"}:
|
if ext not in ALLOWED_EXT:
|
||||||
return None
|
return None
|
||||||
cmd = [exe, "-q", str(quality), "-O", "bigger", "--min-savings", min_savings, "-o", str(out_dir)]
|
cmd = [exe, "-q", str(quality), "-O", "bigger", "--min-savings", min_savings, "-o", str(out_dir)]
|
||||||
if caesium_threads is not None:
|
if caesium_threads is not None:
|
||||||
@@ -187,12 +193,6 @@ def build_image_slide_index(rels_dir: Path) -> dict[str, List[int]]:
|
|||||||
return {img: sorted(slides) for img, slides in image_to_slides.items()}
|
return {img: sorted(slides) for img, slides in image_to_slides.items()}
|
||||||
|
|
||||||
|
|
||||||
def get_slide_numbers_for_image(rels_dir: Path, image_filename: str) -> Optional[List[int]]:
|
|
||||||
image_to_slides = build_image_slide_index(rels_dir)
|
|
||||||
slides = image_to_slides.get(image_filename)
|
|
||||||
return slides if slides else None
|
|
||||||
|
|
||||||
|
|
||||||
def process_image_file(
|
def process_image_file(
|
||||||
idx: int,
|
idx: int,
|
||||||
img_path: Path,
|
img_path: Path,
|
||||||
@@ -243,6 +243,8 @@ def process_single_deck(
|
|||||||
input=str(input_pptx),
|
input=str(input_pptx),
|
||||||
output=str(output_pptx),
|
output=str(output_pptx),
|
||||||
)
|
)
|
||||||
|
work_dir: Optional[Path] = None
|
||||||
|
scratch_dir: Optional[Path] = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not input_pptx.exists() or input_pptx.suffix.lower() != ".pptx":
|
if not input_pptx.exists() or input_pptx.suffix.lower() != ".pptx":
|
||||||
@@ -253,7 +255,6 @@ def process_single_deck(
|
|||||||
|
|
||||||
work_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "work_"))
|
work_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "work_"))
|
||||||
scratch_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "scratch_"))
|
scratch_dir = Path(tempfile.mkdtemp(prefix=TEMP_PREFIX + "scratch_"))
|
||||||
|
|
||||||
log_file = output_pptx.with_suffix(".log.csv")
|
log_file = output_pptx.with_suffix(".log.csv")
|
||||||
ensure_clean_file(log_file)
|
ensure_clean_file(log_file)
|
||||||
log_lines = ["image_name;size_before(kb);size_after(kb);saving(kb);saving_percent(%);in_slide_number\n"]
|
log_lines = ["image_name;size_before(kb);size_after(kb);saving(kb);saving_percent(%);in_slide_number\n"]
|
||||||
@@ -277,7 +278,7 @@ def process_single_deck(
|
|||||||
if not which("caesiumclt") and compressor is compress_with_caesium:
|
if not which("caesiumclt") and compressor is compress_with_caesium:
|
||||||
raise RuntimeError("'caesiumclt' nicht gefunden. Bitte installieren und in PATH verfügbar machen.")
|
raise RuntimeError("'caesiumclt' nicht gefunden. Bitte installieren und in PATH verfügbar machen.")
|
||||||
|
|
||||||
caesium_threads = 1 if threads and threads > 1 else None
|
caesium_threads = 1 if threads > 1 else None
|
||||||
lock = Lock()
|
lock = Lock()
|
||||||
done_count = 0
|
done_count = 0
|
||||||
image_to_slides = build_image_slide_index(rels_dir)
|
image_to_slides = build_image_slide_index(rels_dir)
|
||||||
@@ -303,8 +304,11 @@ def process_single_deck(
|
|||||||
if total > 0:
|
if total > 0:
|
||||||
with ThreadPoolExecutor(max_workers=max(1, threads)) as ex:
|
with ThreadPoolExecutor(max_workers=max(1, threads)) as ex:
|
||||||
futures = [ex.submit(worker, i, p) for i, p in enumerate(images, start=1)]
|
futures = [ex.submit(worker, i, p) for i, p in enumerate(images, start=1)]
|
||||||
for _ in as_completed(futures):
|
for fut in as_completed(futures):
|
||||||
pass
|
try:
|
||||||
|
fut.result()
|
||||||
|
except Exception as exc:
|
||||||
|
sys.stderr.write(f"[worker] Unerwarteter Fehler: {exc}\n")
|
||||||
|
|
||||||
print() # newline
|
print() # newline
|
||||||
|
|
||||||
@@ -351,14 +355,10 @@ def process_single_deck(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
result.error = str(e)
|
result.error = str(e)
|
||||||
finally:
|
finally:
|
||||||
try:
|
if work_dir is not None:
|
||||||
shutil.rmtree(work_dir, ignore_errors=True) # type: ignore[name-defined]
|
shutil.rmtree(work_dir, ignore_errors=True)
|
||||||
except Exception:
|
if scratch_dir is not None:
|
||||||
pass
|
shutil.rmtree(scratch_dir, ignore_errors=True)
|
||||||
try:
|
|
||||||
shutil.rmtree(scratch_dir, ignore_errors=True) # type: ignore[name-defined]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
cleanup_old_temps()
|
cleanup_old_temps()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user