""" Generator PSD — Akrylowe podziekowanie na chrzest dla Matki Chrzestnej (Wzór 1). Plaska struktura PSD: w "Warstwa 1" znajduja sie warstwy tekstowe (imie, naglowek, podziekowanie, data) oraz Smart Object "zdjecie". Podmiana zdjecia: - jesli --zdjecie 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\chrzest - podziękowania", "Akrylowe podziękowanie na chrzest dla Matki Chrzestnej - Wzór 1", ) SZABLON_PATH = os.path.join(PROJEKT_DIR, "Akrylowe podziękowanie na chrzest dla Matki Chrzestnej - Wzór 1 CMYK.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") 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 generate(klient, imie, data, zdjecie=None, naglowek=None, podziekowanie=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}") w1 = doc.layerSets["Warstwa 1"] print(" Podmiana tekstow:") set_layer_text(w1, "imie", imie) set_layer_text(w1, "data", data) set_layer_text(w1, "naglowek", naglowek) set_layer_text(w1, "podziekowanie", podziekowanie) so_layer = w1.artLayers["zdjecie"] if zdjecie and os.path.isfile(zdjecie): print(f" Podmiana zdjecia: {zdjecie}") replace_photo_cover(app, so_layer, zdjecie) else: if zdjecie: print(f" UWAGA: plik zdjecia nie istnieje ({zdjecie}) — wstawiam zielony placeholder") else: print(" Brak --zdjecie — wstawiam zielony placeholder") replace_photo_cover(app, so_layer, ensure_green_placeholder()) psd_opts = ps.PhotoshopSaveOptions() doc.saveAs(output_path, psd_opts, True) print(f"Zapisano: {output_path}") doc.close(ps.SaveOptions.DoNotSaveChanges) print("Gotowe!") return output_path def main(): parser = argparse.ArgumentParser( description="Generator PSD - Akrylowe podziekowanie chrzest, Matka Chrzestna Wzor 1" ) parser.add_argument("--klient", required=True, help="Nazwa pliku wyjsciowego") parser.add_argument("--imie", required=True, help="Imie dziecka (np. Gabrysia)") parser.add_argument("--data", required=True, help="Data uroczystosci (np. 21.09.2025)") parser.add_argument("--zdjecie", default=None, help="Sciezka do zdjecia dziecka (opcjonalnie; brak = zielony placeholder)") parser.add_argument("--naglowek", default=None, help="Naglowek (opcjonalnie, domyslnie 'Kochana Matko Chrzestna')") parser.add_argument("--podziekowanie", default=None, help="Tresc podziekowania (opcjonalnie, domyslna z szablonu)") args = parser.parse_args() generate( klient=args.klient, imie=args.imie, data=args.data, zdjecie=args.zdjecie, naglowek=args.naglowek, podziekowanie=args.podziekowanie, ) if __name__ == "__main__": main()