""" Generator PSD — Akrylowa statuetka podziekowanie dla Rodzicow ze zdjeciem (Wzor 1). Plaska struktura PSD: w "Warstwa 1" znajduja sie: - warstwy tekstowe: naglowek, imiona, data, zyczenia - 4 Smart Objecty ze zdjeciami: zdjecie_01, zdjecie_02, zdjecie_03, zdjecie_04 Podmiana zdjec: - jesli --zdjecieN wskazuje na istniejacy plik graficzny → zostaje wstawione, przeskalowane "cover" do oryginalnych boundsow placeholdera i wycentrowane - jesli zdjecie nie zostalo podane lub plik nie istnieje → wstawiamy zielony prostokat (#00FF00) jako sygnal, ze klient nie przeslal foto Wymaga: uruchomiony Adobe Photoshop, photoshop-python-api, Pillow. """ import argparse import os import photoshop.api as ps PROJEKT_DIR = os.path.join( r"d:\pomysloweprezenty.pl\projekty\ślub - podziękowania", "Akrylowa statuetka podziękowanie dla rodziców ze zdjęciem - Wzór 1", ) SZABLON_PATH = os.path.join(PROJEKT_DIR, "Akrylowa statuetka podziękowanie dla rodziców ze zdjęciem - Wzór 1.psd") GOTOWE_DIR = os.path.join(PROJEKT_DIR, "_gotowe") ASSETS_DIR = os.path.join(os.path.dirname(__file__), "_assets") GREEN_PLACEHOLDER_PATH = os.path.join(ASSETS_DIR, "green_placeholder.png") PHOTO_SLOTS = ("zdjecie_01", "zdjecie_02", "zdjecie_03", "zdjecie_04") def ensure_green_placeholder(): """Tworzy raz zielony PNG (#00FF00) jako placeholder dla brakujacego zdjecia.""" if os.path.exists(GREEN_PLACEHOLDER_PATH): return GREEN_PLACEHOLDER_PATH os.makedirs(ASSETS_DIR, exist_ok=True) from PIL import Image Image.new("RGB", (1000, 1000), (0, 255, 0)).save(GREEN_PLACEHOLDER_PATH) return GREEN_PLACEHOLDER_PATH def set_text(layer, new_text): """Zmienia tekst zachowujac srodek bounding boxa (centrowane warstwy).""" b = [float(x) for x in layer.bounds] cx, cy = (b[0] + b[2]) / 2, (b[1] + b[3]) / 2 layer.textItem.contents = new_text b2 = [float(x) for x in layer.bounds] cx2, cy2 = (b2[0] + b2[2]) / 2, (b2[1] + b2[3]) / 2 dx, dy = cx - cx2, cy - cy2 if dx or dy: layer.translate(dx, dy) def set_layer_text(container, layer_name, new_text): if new_text is None: return layer = container.artLayers[layer_name] old = layer.textItem.contents set_text(layer, new_text) print(f' {layer_name}: "{old}" -> "{new_text}"') def replace_smart_object_contents(app, file_path): """Wykonuje akcje placedLayerReplaceContents na aktywnej warstwie SO.""" desc = ps.ActionDescriptor() desc.putPath(app.charIDToTypeID("null"), file_path) desc.putInteger(app.charIDToTypeID("PgNm"), 1) app.executeAction(app.stringIDToTypeID("placedLayerReplaceContents"), desc) def replace_photo_cover(app, so_layer, photo_path): """Podmienia zawartosc Smart Object i skaluje 'cover' do oryginalnych boundsow.""" target = [float(x) for x in so_layer.bounds] target_w = target[2] - target[0] target_h = target[3] - target[1] target_cx = (target[0] + target[2]) / 2 target_cy = (target[1] + target[3]) / 2 app.activeDocument.activeLayer = so_layer replace_smart_object_contents(app, photo_path) new_b = [float(x) for x in so_layer.bounds] new_w = new_b[2] - new_b[0] new_h = new_b[3] - new_b[1] if new_w <= 0 or new_h <= 0: return scale = max(target_w / new_w, target_h / new_h) * 100.0 so_layer.resize(scale, scale, ps.AnchorPosition.MiddleCenter) cur = [float(x) for x in so_layer.bounds] cur_cx = (cur[0] + cur[2]) / 2 cur_cy = (cur[1] + cur[3]) / 2 so_layer.translate(target_cx - cur_cx, target_cy - cur_cy) def swap_photo_slot(app, container, slot_name, photo_path): """Podmienia zdjecie w slocie; brak/nieistnieje → zielony placeholder.""" so_layer = container.artLayers[slot_name] if photo_path and os.path.isfile(photo_path): print(f" {slot_name}: {photo_path}") replace_photo_cover(app, so_layer, photo_path) else: if photo_path: print(f" {slot_name}: UWAGA plik nie istnieje ({photo_path}) — zielony placeholder") else: print(f" {slot_name}: brak — zielony placeholder") replace_photo_cover(app, so_layer, ensure_green_placeholder()) def generate(klient, imiona, data, naglowek=None, zyczenia=None, zdjecie1=None, zdjecie2=None, zdjecie3=None, zdjecie4=None): os.makedirs(GOTOWE_DIR, exist_ok=True) output_path = os.path.join(GOTOWE_DIR, f"{klient}.psd") app = ps.Application() doc = app.open(SZABLON_PATH) print(f"Otwarto szablon: {doc.name}") try: w1 = doc.layerSets["Warstwa 1"] print(" Podmiana tekstow:") set_layer_text(w1, "naglowek", naglowek) set_layer_text(w1, "imiona", imiona) set_layer_text(w1, "data", data) set_layer_text(w1, "zyczenia", zyczenia) print(" Podmiana zdjec:") zdjecia = [zdjecie1, zdjecie2, zdjecie3, zdjecie4] for slot, photo in zip(PHOTO_SLOTS, zdjecia): swap_photo_slot(app, w1, slot, photo) psd_opts = ps.PhotoshopSaveOptions() doc.saveAs(output_path, psd_opts, True) print(f"Zapisano: {output_path}") finally: try: doc.close(ps.SaveOptions.DoNotSaveChanges) except Exception: pass print("Gotowe!") return output_path def main(): parser = argparse.ArgumentParser( description="Generator PSD - Akrylowa statuetka podziekowanie dla Rodzicow Wzor 1" ) parser.add_argument("--klient", required=True, help="Nazwa pliku wyjsciowego (bez rozszerzenia)") parser.add_argument("--imiona", required=True, help="Imiona pary mlodej (np. 'Kasia i Tomek')") parser.add_argument("--data", required=True, help="Data uroczystosci (np. '10.08.2025')") parser.add_argument("--naglowek", default=None, help="Naglowek (opcjonalnie, domyslnie 'Kochani Rodzice!')") parser.add_argument("--zyczenia", default=None, help="Tresc podziekowania (opcjonalnie)") parser.add_argument("--zdjecie1", default=None, help="Sciezka do zdjecia 1 (opcjonalnie; brak = zielony placeholder)") parser.add_argument("--zdjecie2", default=None, help="Sciezka do zdjecia 2") parser.add_argument("--zdjecie3", default=None, help="Sciezka do zdjecia 3") parser.add_argument("--zdjecie4", default=None, help="Sciezka do zdjecia 4") args = parser.parse_args() generate( klient=args.klient, imiona=args.imiona, data=args.data, naglowek=args.naglowek, zyczenia=args.zyczenia, zdjecie1=args.zdjecie1, zdjecie2=args.zdjecie2, zdjecie3=args.zdjecie3, zdjecie4=args.zdjecie4, ) if __name__ == "__main__": main()