From 3c65f8cf65a188805e2df4a353b626ab876471ce Mon Sep 17 00:00:00 2001 From: Frank Conrads Date: Wed, 10 Jun 2026 12:43:12 +0200 Subject: [PATCH] =?UTF-8?q?SVG-Compression=20Profile=20balanced=20ist=20we?= =?UTF-8?q?sentlich=20konservativer,=20Datei-Eigenschaften=20der=20neuen?= =?UTF-8?q?=20PPTX=20enth=C3=A4lt=20Hinweis=20auf=20PPTX=20Image=20Compres?= =?UTF-8?q?sor=20in=20den=20Kommentaren?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 ++++--- pptx_image_compress.py | 65 +++++++++++++++++++++++++++++-------- test_pptx_image_compress.py | 47 +++++++++++++-------------- 3 files changed, 79 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 2901581..d0020ed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PPTX Image Compressor (CaesiumCLT only) -**Version 1.1.8** +**Version 1.1.9** Dieses Paket enthält: @@ -33,22 +33,23 @@ Zusätzlich wird **pip** installiert, damit das **svg-polish** Modul installiert ## Was das Tool macht - Entpackt die PPTX in einen Temp‑Ordner - Komprimiert **JPG/JPEG, PNG, WebP, GIF** mit **CaesiumCLT** (Default `-q 90`, `-O bigger`) -- Komprimiert **SVG** mit **svg-polish** (Default-Modus: `agressive`) +- Komprimiert **SVG** mit **svg-polish** (Default-Modus: `balanced`) - Ersetzt Bilder nur, wenn die komprimierte Datei kleiner ist - Versucht bei PNG zusätzlich einen PNG->JPG Wechsel, wenn das Bild nach Kompression noch größer als 500 KB ist - Ersetzt Bilder nur, wenn sei mindestens 2% kleiner sind (verhindert *doppelte Komprimierung*) - Schreibt ein CSV‑Log (`.log` neben der Output‑PPTX) - Baut eine neue PPTX und zeigt eine Summary (Name, Größe vorher/nachher, Ersparnis %, Zeit) -## Änderungen in 1.1.8 -- SVG Files werden bei Vorhandensein von svg-polish anhand von 2 Profilen optimiert: balanced|agressive +## Änderungen in 1.1.9 +- SVG Files Default Profile: `balanced` statt `aggressive` +- Datei-Eigenschaften der neu generierten PPTX enthält Hinweis auf Compression `compressed by PPTX Image Compressor` ## 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 - `-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`). -- `--svg-profile` steuert das Vector-Optimierungsprofil `balanced|agressive` +- `--svg-profile` steuert das Vector-Optimierungsprofil `balanced|aggressive` ## Manuelle Nutzung des .py (falls Python vorhanden) ```bat diff --git a/pptx_image_compress.py b/pptx_image_compress.py index e96efb9..6596772 100644 --- a/pptx_image_compress.py +++ b/pptx_image_compress.py @@ -2,7 +2,11 @@ # -*- coding: utf-8 -*- """ PPTX Raster & Vector Komprimier-Tool (Raster-Iamges: via CaesiumCLT, Vector-Images: via python Module svg_polish) -Version: 1.1.8 +Version: 1.1.9 + +Änderungen in 1.1.9 +- SVG Files Default Profile: `balanced` statt `aggressive` +- Datei-Eigenschaften der neu generierten PPTX enthält Hinweis auf Compression `compressed by PPTX Image Compressor` Änderungen in 1.1.8: - SVG Files werden bei Vorhandensein von svg_polish anhand von 2 Profilen optimiert: balanced|agressive @@ -38,7 +42,7 @@ from typing import Callable, List, Optional -__version__ = "1.1.8" +__version__ = "1.1.9" RASTER_EXT = {".jpg", ".jpeg", ".png", ".webp", ".gif"} VECTOR_EXT = {".svg"} @@ -50,7 +54,7 @@ PNG_TO_JPEG_THRESHOLD_BYTES = 500 * 1024 SVG_POLISH_MODULE_NAME = "svg_polish" SVG_PROFILE_BALANCED = "balanced" SVG_PROFILE_AGGRESSIVE = "aggressive" -SVG_PROFILE_DEFAULT = SVG_PROFILE_AGGRESSIVE +SVG_PROFILE_DEFAULT = SVG_PROFILE_BALANCED @dataclass @@ -252,17 +256,8 @@ def build_svg_polish_options(svg_polish_module: object, profile: str = SVG_PROFI try: if profile == SVG_PROFILE_BALANCED: return options_type( - digits=3, - style_to_xml=True, - group_collapse=True, - simple_colors=True, - indent_type="none", - newlines=False, - strip_xml_prolog=True, - strip_comments=True, - remove_metadata=True, - remove_titles=True, - remove_descriptions=True, + shorten_ids=True, + enable_viewboxing=True, ) return options_type( digits=2, @@ -648,6 +643,8 @@ def process_single_deck( except Exception: pass + update_core_description(work_dir, "PPTX Image Compressor",__version__) + zip_dir_to_pptx(work_dir, output_pptx) size_after = output_pptx.stat().st_size result.size_after = size_after @@ -845,6 +842,46 @@ def extractParserArguments(): +def update_core_description(base_dir, app_name, version): + core_xml_path = Path(base_dir) / "docProps" / "core.xml" + + if not core_xml_path.exists(): + raise FileNotFoundError(f"{core_xml_path} nicht gefunden") + + # Namespaces definieren + ns = { + "cp": "http://schemas.openxmlformats.org/package/2006/metadata/core-properties", + "dc": "http://purl.org/dc/elements/1.1/", + "dcterms": "http://purl.org/dc/terms/", + "xsi": "http://www.w3.org/2001/XMLSchema-instance" + } + + # Registrieren, damit Prefixe erhalten bleiben + for prefix, uri in ns.items(): + ET.register_namespace(prefix, uri) + + tree = ET.parse(core_xml_path) + root = tree.getroot() + + description_text = f"compressed by {app_name} {version}" + + # Suche vorhandenes Element + desc_elem = root.find("dc:description", ns) + + if desc_elem is None: + # neu anlegen + desc_elem = ET.SubElement( + root, + f"{{{ns['dc']}}}description" + ) + + # Text setzen/überschreiben + desc_elem.text = description_text + + # Datei speichern + tree.write(core_xml_path, encoding="utf-8", xml_declaration=True) + + if __name__ == '__main__': main() diff --git a/test_pptx_image_compress.py b/test_pptx_image_compress.py index 74b561c..2efbc5b 100644 --- a/test_pptx_image_compress.py +++ b/test_pptx_image_compress.py @@ -155,15 +155,17 @@ class TestPptxImageCompress(unittest.TestCase): out.write_bytes(b"B" * 50) return out - result = pic.process_single_deck( - input_pptx=input_pptx, - output_pptx=output_pptx, - threads=2, - quality=90, - min_savings="2%", - compressor=fake_compressor, - ) + with mock.patch("pptx_image_compress.update_core_description", create=True) as mocked_update_core_description: + result = pic.process_single_deck( + input_pptx=input_pptx, + output_pptx=output_pptx, + threads=2, + quality=90, + min_savings="2%", + compressor=fake_compressor, + ) + mocked_update_core_description.assert_called_once_with(mock.ANY, "PPTX Image Compressor", pic.__version__) self.assertTrue(result.ok) self.assertEqual(result.error, None) self.assertTrue(output_pptx.exists()) @@ -222,15 +224,17 @@ class TestPptxImageCompress(unittest.TestCase): out.write_bytes(b"B" * 700000) return out - result = pic.process_single_deck( - input_pptx=input_pptx, - output_pptx=output_pptx, - threads=1, - quality=90, - min_savings="2%", - compressor=fake_compressor, - ) + with mock.patch("pptx_image_compress.update_core_description", create=True) as mocked_update_core_description: + result = pic.process_single_deck( + input_pptx=input_pptx, + output_pptx=output_pptx, + threads=1, + quality=90, + min_savings="2%", + compressor=fake_compressor, + ) + mocked_update_core_description.assert_called_once_with(mock.ANY, "PPTX Image Compressor", pic.__version__) self.assertTrue(result.ok) with zipfile.ZipFile(output_pptx, "r") as z: self.assertIn("ppt/media/image1.jpg", z.namelist()) @@ -314,13 +318,6 @@ class TestPptxImageCompress(unittest.TestCase): result = pic.optimize_svg_content_with_module(fake_module, svg) self.assertEqual(result, "") - self.assertEqual(captured_options["digits"], 2) - self.assertEqual(captured_options["indent_type"], "none") - self.assertEqual(captured_options["newlines"], False) - self.assertEqual(captured_options["strip_xml_prolog"], True) - self.assertEqual(captured_options["strip_comments"], True) - self.assertEqual(captured_options["strip_ids"], True) - self.assertEqual(captured_options["renderer_workaround"], False) self.assertEqual(fake_module.optimize_path.call_count, 1) def test_build_svg_polish_options_balanced_profile(self): @@ -336,8 +333,8 @@ class TestPptxImageCompress(unittest.TestCase): options = pic.build_svg_polish_options(fake_module, pic.SVG_PROFILE_BALANCED) self.assertIsNotNone(options) - self.assertEqual(captured_options["digits"], 3) - self.assertEqual(captured_options["group_collapse"], True) + self.assertEqual(captured_options["shorten_ids"], True) + self.assertEqual(captured_options["enable_viewboxing"], True) self.assertNotIn("strip_ids", captured_options) def test_compress_svg_with_svg_polish_returns_none_when_module_missing(self):