From d7dd117dab28056776a8215c5b3fb3f35326a106 Mon Sep 17 00:00:00 2001 From: DJP Date: Mon, 2 Mar 2026 17:03:07 -0500 Subject: [PATCH] Fix Adobe API text updates: correct field names, font handling, and GCS bucket - Fix API payload: "contents" not "content", "align" not "alignment", output type "vnd.adobe.photoshop" not "image/vnd.adobe.photoshop" - Remove broken font size /72 conversion (values already in points) - Add automatic font upload from fonts/ directory to GCS - Add FuturaPT-Demi.otf extracted from Adobe CoreSync - Update GCS bucket to lor-txt-tmp-bkt-26 (old billing expired) - Update HOW-IT-WORKS.md with working API docs, font setup guide, bug fixes, and verified test results Co-Authored-By: Claude Opus 4.6 --- ARCHIVE/debug_text_layer.py | 2 +- HOW-IT-WORKS.md | 302 ++++++++++++++++++++++++------------ adobe_ps_api.py | 2 +- config.py | 2 +- fonts/FuturaPT-Demi.otf | Bin 0 -> 114596 bytes gcs_key.json | 14 +- simplified_payload.py | 110 ++++++------- updated_text_payload.py | 2 +- 8 files changed, 262 insertions(+), 172 deletions(-) create mode 100644 fonts/FuturaPT-Demi.otf diff --git a/ARCHIVE/debug_text_layer.py b/ARCHIVE/debug_text_layer.py index 18a1fdd..2ede9c0 100644 --- a/ARCHIVE/debug_text_layer.py +++ b/ARCHIVE/debug_text_layer.py @@ -31,7 +31,7 @@ logger = logging.getLogger(__name__) token_manager = AdobeTokenManager(config.ADOBE_CLIENT_ID, config.ADOBE_CLIENT_SECRET) # GCS bucket configuration -GCS_BUCKET_NAME = "lor-txt-tmp-bkt" # The bucket to use for temporary storage +GCS_BUCKET_NAME = "lor-txt-tmp-bkt-26" # The bucket to use for temporary storage GCS_KEY_PATH = os.path.join(os.path.dirname(__file__), "gcs_key.json") # Initialize GCS storage if the key exists diff --git a/HOW-IT-WORKS.md b/HOW-IT-WORKS.md index 3d877b4..f3b19e1 100644 --- a/HOW-IT-WORKS.md +++ b/HOW-IT-WORKS.md @@ -4,8 +4,10 @@ This project automates **extracting and updating text layers** in Photoshop PSD files. It provides two approaches: -1. **Local workflow** (recommended, working) - Python drives Photoshop directly via AppleScript + ExtendScript -2. **Adobe Cloud API workflow** (experimental, text updates not applying) - Files uploaded to Google Cloud Storage, processed via Adobe's Photoshop API +1. **Local workflow** - Python drives Photoshop directly via AppleScript + ExtendScript +2. **Adobe Cloud API workflow** - Files uploaded to Google Cloud Storage, processed via Adobe's Photoshop API + +Both workflows are fully functional. The API workflow updates text, preserves font size, and supports custom fonts. --- @@ -16,85 +18,158 @@ This project automates **extracting and updating text layers** in Photoshop PSD | ADOBE PSD TEXT MANAGEMENT | +============================================================================+ - WORKFLOW A: LOCAL (macOS - RECOMMENDED) WORKFLOW B: ADOBE CLOUD API - ==================================== ============================ + WORKFLOW A: LOCAL (macOS) WORKFLOW B: ADOBE CLOUD API + ======================== ========================== +-------------+ +-------------+ | PSD File | | PSD File | +------+------+ +------+------+ | | v v - +-----------------+ +----------------+ - | mac_ps_extract | | adobe_ps_api | - | .py | | simplified_ | - | | | payload.py | - +--------+--------+ +-------+--------+ - | | - | AppleScript | - v v - +-----------------+ +----------------+ - | Photoshop | | gcs_storage | - | (ExtendScript) | | .py | - | | +-------+--------+ - | ExtractText | | - | WithBreaks.jsx | | Upload PSD - +--------+--------+ v - | +----------------+ - | JSON output | Google Cloud | - v | Storage Bucket | - +-----------------+ | (lor-txt-tmp) | - | *-textonly.json | +-------+--------+ - | { | | - | "textLayers": | | Signed URLs - | [{ | v - | "name": "..", | +----------------+ - | "text": "..", | | adobe_token.py | - | "styleInfo": | | (OAuth 2.0) | - | {font,size, | +-------+--------+ - | color} | | - | }] | | Bearer Token - | } | v - +--------+--------+ +----------------+ - | | Adobe IMS | - | User edits | ims-na1.adobe | - | "updatedText" | login.com | - v +-------+--------+ - +-----------------+ | - | *-textonly.json | | Authenticated - | (edited) | v - +--------+--------+ +------------------+ - | | Adobe Photoshop | - v | API Endpoint | - +-----------------+ | image.adobe.io/ | - | mac_ps_update | | pie/psdService/ | - | .py | | text | + +-----------------+ +------------------+ + | mac_ps_extract | | simplified_ | + | .py | | payload.py | +--------+--------+ +--------+---------+ | | - | AppleScript | 202 Accepted - v | + Status URL - +-----------------+ v - | Photoshop | +------------------+ - | (ExtendScript) | | Poll status URL | - | | | until "succeeded"| - | updateText | +--------+---------+ - | Layers.jsx | | - +--------+--------+ v - | +------------------+ - v | Download result | - +-----------------+ | from GCS bucket | - | Updated PSD | +--------+---------+ - | (text changed, | | - | styles kept) | v - +-----------------+ +------------------+ - | api_updated_*.psd| - | (text NOT | - | changing - known| - | issue) | - +------------------+ + | AppleScript +---------+---------+ + v | | + +-----------------+ +-----+------+ +-------+------+ + | Photoshop | | Upload PSD | | Upload fonts | + | (ExtendScript) | | to GCS | | from fonts/ | + | | +-----+------+ +-------+------+ + | ExtractText | | | + | WithBreaks.jsx | v v + +--------+--------+ +---------------------------+ + | | Google Cloud Storage | + | JSON output | Bucket: lor-txt-tmp-bkt-26| + v +------------+--------------+ + +-----------------+ | + | *-textonly.json | Signed URLs (input, + | {textLayers:[]} | output, fonts) + +--------+--------+ | + | v + | User edits +------------------+ + | "updatedText" | adobe_token.py | + v | (OAuth 2.0) | + +-----------------+ +--------+---------+ + | *-textonly.json | | + | (edited) | | Bearer Token + +--------+--------+ v + | +------------------+ + v | Adobe Photoshop | + +-----------------+ | API Endpoint | + | mac_ps_update | | image.adobe.io/ | + | .py | | pie/psdService/ | + +--------+--------+ | text | + | +--------+---------+ + | AppleScript | + v | 202 Accepted + +-----------------+ v + | Photoshop | +------------------+ + | updateText | | Poll status URL | + | Layers.jsx | | until "succeeded"| + +--------+--------+ +--------+---------+ + | | + v v + +-----------------+ +------------------+ + | Updated PSD | | Download result | + | (text changed, | | from GCS bucket | + | styles kept) | +--------+---------+ + +-----------------+ | + v + +------------------+ + | api_updated_*.psd| + | Text changed, | + | font + size kept | + +------------------+ ``` --- +## API Workflow — How It Actually Works + +The `simplified_payload.py` script handles the full pipeline: + +``` + 1. UPLOAD 2. AUTHENTICATE 3. BUILD PAYLOAD + ======== =============== =============== + + PSD file ──> GCS bucket config.py credentials { + fonts/ dir ──> GCS bucket ──> Adobe IMS OAuth "inputs": [signed_url], + ──> Bearer token "options": { + Generate signed URLs "fonts": [font_urls], + for input + output "layers": [{ + "name": "Layer Name", + "text": { + 4. API CALL 5. POLL "contents": "NEW TEXT", + ========== ===== "characterStyles": [{ + "fontPostScriptName": + POST payload to GET status URL "FuturaPT-Demi" + image.adobe.io/ every 5 seconds }] + pie/psdService/text until "succeeded" }}] + }, + Returns 202 + "outputs": [signed_url] + status URL } + + 6. DOWNLOAD 7. CLEANUP + ========== ========= + + Download processed PSD Delete temporary files + from GCS output URL from GCS bucket + Save as api_updated_* +``` + +### Key API Details + +- **Endpoint**: `POST https://image.adobe.io/pie/psdService/text` +- **Auth headers**: `Authorization: Bearer ` + `x-api-key: ` +- **Layer matching**: Uses layer `name` (not numeric ID) +- **Text field**: Must be `"contents"` (not `"content"`) +- **Paragraph alignment**: Must be `"align"` (not `"alignment"`) +- **Font size**: Do NOT include `size` in characterStyles — the API preserves the original size automatically. Only specify `fontPostScriptName` to keep the correct font. +- **Custom fonts**: Uploaded to GCS and referenced via `options.fonts` array with signed URLs +- **Output type**: `"vnd.adobe.photoshop"` (not `"image/vnd.adobe.photoshop"`) + +--- + +## Custom Fonts Setup + +The Adobe cloud API does NOT have access to Adobe Fonts (Typekit) or your local fonts. You must provide any non-standard fonts yourself. + +### Where to find Adobe Fonts on macOS + +Adobe Fonts are hidden in: +``` +~/Library/Application Support/Adobe/CoreSync/plugins/livetype/.w/ +``` + +Files are named with numeric IDs (e.g., `.15586.otf`). To find a specific font: + +```bash +# Search for a font by PostScript name +find ~/Library/Application\ Support/Adobe/CoreSync/plugins/livetype/ \ + -name "*.otf" -exec sh -c \ + 'strings "$1" | grep -q "FuturaPT-Demi" && echo "FOUND: $1"' _ {} \; +``` + +### Adding fonts to the project + +1. Find the font file (see above) +2. Copy it to the `fonts/` directory with a descriptive name: + ```bash + cp ~/.../livetype/.w/.15586.otf fonts/FuturaPT-Demi.otf + ``` +3. The script automatically uploads ALL files from `fonts/` to GCS and includes them in the API payload + +### Currently included fonts + +| File | PostScript Name | Used By | +|------|----------------|---------| +| `FuturaPT-Demi.otf` | FuturaPT-Demi | Vichy product PSDs | +| `Futura.ttc` | Futura-Medium, Futura-Bold, etc. | General Futura variants | + +--- + ## Authentication & Credentials Map ``` @@ -116,7 +191,7 @@ This project automates **extracting and updating text layers** in Photoshop PSD | Adobe IMS OAuth | | Google Cloud | | Token Endpoint | | Storage API | | (ims-na1.adobe | | Bucket: | - | login.com) | | lor-txt-tmp-bkt | + | login.com) | | lor-txt-tmp-bkt-26| +------------------+ +-------------------+ OAUTH FLOW: @@ -153,7 +228,7 @@ This project automates **extracting and updating text layers** in Photoshop PSD | `mac_ps_update.py` | Python orchestrator for local updates | AppleScript -> Photoshop | | `updateTextLayers.jsx` | ExtendScript that writes to PSD layers | Runs inside Photoshop | | `batch_update_text.py` | Batch wrapper for multiple updates | `mac_ps_update.py` | -| `simplified_payload.py` | Sends update via Adobe API | `adobe_token.py`, `gcs_storage.py` | +| `simplified_payload.py` | Sends update via Adobe API (recommended) | `adobe_token.py`, `gcs_storage.py` | | `update_text_with_api.py` | Batch API updates | `adobe_token.py`, `gcs_storage.py` | ### Layer ID Resolution @@ -168,7 +243,7 @@ This project automates **extracting and updating text layers** in Photoshop PSD ## Data Flow Step-by-Step -### Local Workflow (What Actually Works) +### Local Workflow ``` STEP 1: EXTRACT STEP 2: EDIT STEP 3: UPDATE @@ -197,30 +272,23 @@ This project automates **extracting and updating text layers** in Photoshop PSD Saves *-textonly.json ``` -### API Workflow (Experimental) +### API Workflow ``` - STEP 1: UPLOAD STEP 2: AUTHENTICATE STEP 3: API CALL - ============== ==================== ================ + STEP 1: PREP JSON STEP 2: RUN API STEP 3: VERIFY + ================ =========== ============= - gcs_storage.py adobe_token.py POST to image.adobe.io - uploads PSD to gets OAuth bearer /pie/psdService/text - GCS bucket token from Adobe IMS - Payload: - Generates signed Token cached in { - URLs for input .adobe_token_cache.json inputs: [signed_url], - and output options: { - Auto-refreshes layers: [{ - before expiry id: LAYER_ID, - text: {content: "NEW"} - }] - }, - STEP 4: POLL STEP 5: DOWNLOAD outputs: [signed_url] - =========== =============== } - Returns: 202 + status URL - Check status URL Download processed - every few seconds PSD from GCS output - until "succeeded" signed URL + Extract text to JSON $ python simplified_payload Open the output + (or use existing) .py file.json file.psd api_updated_*.psd + in Photoshop + Edit "updatedText" Script will: + fields in the JSON 1. Upload PSD to GCS Check that: + 2. Upload fonts/ to GCS - Text changed + Make sure fonts/ 3. Get OAuth token - Font preserved + directory has the 4. POST to Adobe API - Size preserved + required .otf files 5. Poll until done + 6. Download result + 7. Clean up GCS ``` --- @@ -270,7 +338,7 @@ This project automates **extracting and updating text layers** in Photoshop PSD ## Quick Command Reference ```bash -# --- LOCAL WORKFLOW (recommended) --- +# --- LOCAL WORKFLOW --- # Extract text from PSDs to JSON python mac_ps_extract.py /path/to/psd_folder @@ -284,7 +352,7 @@ python mac_ps_update.py /path/to/json_folder -p /path/to/psd_folder --dry-run # Update text (for real, with save) python mac_ps_update.py /path/to/json_folder -p /path/to/psd_folder --save -# --- API WORKFLOW (experimental) --- +# --- API WORKFLOW --- # Generate OAuth token python adobe_ps_api.py generate-token @@ -301,10 +369,46 @@ python update_text_with_api.py --directory /path/to/directory --- -## Known Issues +## GCS Setup (If Starting Fresh) -- **API text updates don't apply**: The Adobe API returns success (202 -> "succeeded") but the output PSD has unchanged text. This is under investigation (see `API-LAYER-ID-SOLUTION.md`). The layer ID mapping between Photoshop's internal IDs and the API's expected IDs may be the root cause. -- **Local workflow is reliable**: `mac_ps_extract.py` + `mac_ps_update.py` work correctly and preserve formatting. +If the GCS bucket or billing expires, set up a new one: + +1. **Create/re-enable a GCP project** at https://console.cloud.google.com +2. **Enable Cloud Storage API** at https://console.cloud.google.com/apis/library/storage.googleapis.com +3. **Create a bucket** at https://console.cloud.google.com/storage/browser + - Name: e.g. `lor-txt-tmp-bkt-26` + - Location: `us-central1`, Standard class, Uniform access +4. **Create a service account** at https://console.cloud.google.com/iam-admin/serviceaccounts + - Role: `Storage Admin` + - Download JSON key -> save as `gcs_key.json` in project root +5. **Update bucket name** in `config.py` and `simplified_payload.py` if it changed + +--- + +## Bugs Fixed (March 2026) + +These issues were identified and fixed during API testing: + +| Bug | Cause | Fix | +|-----|-------|-----| +| Font size destroyed (0.12pt) | Script divided size by 72 (already in points) | Removed `/72` conversion | +| Text not changing | API field was `"content"` | Changed to `"contents"` (per API spec) | +| Font replaced with Arial | Adobe cloud doesn't have Adobe Fonts/Typekit | Upload actual `.otf` files via `options.fonts` | +| Paragraph alignment ignored | Field was `"alignment"` | Changed to `"align"` (per API spec) | +| Output type rejected | Used `"image/vnd.adobe.photoshop"` | Changed to `"vnd.adobe.photoshop"` | +| GCS billing expired | Old GCP project billing closed | Created new bucket `lor-txt-tmp-bkt-26` | + +--- + +## Test Results (Verified March 2, 2026) + +Using `Vichy-Product-Skincare-Liftactiv...Texture.psd` (single text layer): + +| Property | Original | API Updated | Match? | +|----------|----------|-------------|--------| +| Text | `QUICK ABSORBTION NON-greasyTEXTURE` | `FAST ABSORPTION LIGHTWEIGHT TEXTURE` | Changed | +| Font | `FuturaPT-Demi` | `FuturaPT-Demi` | Exact | +| Size | `32.7pt` | `32.7pt` | Exact | --- @@ -315,5 +419,5 @@ python update_text_with_api.py --directory /path/to/directory - **AppleScript** - Bridge between Python and Photoshop on macOS - **Adobe Photoshop API** - Cloud-based PSD manipulation (`image.adobe.io`) - **Adobe IMS OAuth 2.0** - API authentication (`ims-na1.adobelogin.com`) -- **Google Cloud Storage** - Temporary file hosting for API workflow +- **Google Cloud Storage** - Temporary file hosting for API workflow (`lor-txt-tmp-bkt-26`) - **requests, google-cloud-storage** - Python libraries (see `requirements.txt`) diff --git a/adobe_ps_api.py b/adobe_ps_api.py index 7e296fe..96a15f0 100755 --- a/adobe_ps_api.py +++ b/adobe_ps_api.py @@ -48,7 +48,7 @@ from gcs_storage import GCSStorage token_manager = AdobeTokenManager(config.ADOBE_CLIENT_ID, config.ADOBE_CLIENT_SECRET) # GCS bucket configuration -GCS_BUCKET_NAME = "lor-txt-tmp-bkt" # The bucket to use for temporary storage +GCS_BUCKET_NAME = "lor-txt-tmp-bkt-26" # The bucket to use for temporary storage GCS_KEY_PATH = os.path.join(os.path.dirname(__file__), "gcs_key.json") # Initialize GCS storage if the key exists diff --git a/config.py b/config.py index 9ea9e85..76d6e7e 100644 --- a/config.py +++ b/config.py @@ -12,7 +12,7 @@ ADOBE_CLIENT_ID = "f34becb759244899bd73b86220f6fb92" ADOBE_CLIENT_SECRET = "p8e-AGmZ6TSfKhkgiTzhn3_yxw0JSV_yT_Cb" # Client secret confirmed working # GCS configuration -GCS_BUCKET_NAME = "lor-txt-tmp-bkt" +GCS_BUCKET_NAME = "lor-txt-tmp-bkt-26" GCS_KEY_PATH = "gcs_key.json" # Default API scopes for Photoshop REST API diff --git a/fonts/FuturaPT-Demi.otf b/fonts/FuturaPT-Demi.otf new file mode 100644 index 0000000000000000000000000000000000000000..147254682123ba56c4bc873234d89819e3df5444 GIT binary patch literal 114596 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 literal 0 HcmV?d00001 diff --git a/gcs_key.json b/gcs_key.json index fb851b2..dbcfecd 100644 --- a/gcs_key.json +++ b/gcs_key.json @@ -1,13 +1,13 @@ { "type": "service_account", - "project_id": "optical-visual-intelligence", - "private_key_id": "e9667ea789930c2f32224a2a42c14e93e04577f2", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzLolpd6agLLez\n3ppUSAtZvhXvyKlYxgdNbA3Zert2fZdUdHFxdtRu9rbVwdrX3aNaVI+vyx1Z+z0r\nGnfGIVlI9yUDLil3X/Y3LKNGisM2wcrHpYk/08rm9fQreDjEDnKcwHdbfE2K+LIC\nnt6WpLeE45y8vHb74oBPvGLp0tX/UGvA2qmn9wyKIpJv5TW27e1mTaqNKR6gwqK3\nG044UF32Jf0kkbXlGfzL7QxTfG2LVPpDsU+cnhjmi9wbHv43T2w3hrG/vpYomGiI\nyFFfNGWJtTo+iENZS8sXt2bjNbAP6mWJapjkBz+vGrq6gJ6dgTwOMmqwkFT75dLD\n38X9Bzb7AgMBAAECggEAI/Lfvcm0I+mk15D/HI6xvEkPRKLZGwbr+yQFKU9e4fhz\njo2oHvAM0dBswTkZ63o7pklV5JqXInDuBkJSqm0RaTqwcdFmn10g3FP31BTN8qPx\nx5lWDniy+iB5YulFVkxhwUI6ECBBvHKF4FG4bbfRW/eAgDs9ke6Q+K5el/PlXEoT\nMWcsk9kjfP/7hJqjI2yzCtOLhdC9ZHutWbzsLHuAk40O/qykywqHefrTTC9l/f5e\nNVahZ5hzzAknXRizGHDtV/ejh7l7BNeGHu+091RfHoZ7IMy6564oigkKnVVrZkWj\ntkdRoBKcN6cMjQRcOAhdeV+ZH0aeumzMRaFU3NbvRQKBgQDxFzUu4XnCDcRcP+pF\nqSofeiU38Iunt267zpZYrDAMQGyVw5yefTpM+B9P8jc+kZAiitNVtZVscIrIbAM1\nAmEkAHf4E0/I5s+a/3a4UpScb6lmCMvuvGcLrKvm7sDJlGXrfccH5wusF4pL5wX5\nppwTfVB9M0+46ZB7EGxxAH6wjQKBgQC+QzpCXGJMk301GG2ozW4CyuHaT6o5FJao\nZF8wdJVt8y2oX91Hxm7MzGO1BSeCBtEEZE1+u0IzGHZHJdgxRUj6IhG+QGxff7Uj\nlZuL4w8gC+23O7S5Oa71Na+jThDycWlu8IHMNUGBu6f8zx6bBvJKcF3LmDkkkv/s\nni/3XLL3pwKBgQCv+z7I77EO4zm4FLePDcI/o8tTH/TxAcaEtHGuXFHeP5CDaXwD\nfGl4EY3Zr3Z/54UMkcVdxORDeYr0bVOR+CCsRONNY9tTTJeyDlO8jBsKbb97SWSC\n6WdWcD4ynYiAHCChWvhTXmV4wt4iNYp5BxLabxi3qyLAWU0rZ3ugqLnRaQKBgClP\nBVYlIr6HgzbE8AInYAxBKlow07+C5db3u+cUWOE/XBljfvK3dZUHh1plHRfRDQ6M\nDHtIgu3/EKcP42mHJnoQbZPF/wGZA6YPNG9hxAXsMReIYguZJ5BbsJ+fMnTBBOgu\nVbAVm/xj1uw/t+Bm2LIqxWKP0VBMjj48diOZv82fAoGAb1nK6BczREhOF4eXkg0x\n/tfEqUj5NQvXN9xB+nNy00qd799ORd2xFokMKuGrJYfooSwyyTjojwrDBstFG69B\nn2ZIvMHeOwk3YEktikj1s9YwrK6NTZtqz6bTHsexOMbqYkSmoR2IBhATGLehoQqo\nHilZ1ntXHaHBUzvbNarWcvU=\n-----END PRIVATE KEY-----\n", - "client_email": "lor-txt-tmp-bkt@optical-visual-intelligence.iam.gserviceaccount.com", - "client_id": "106009318690078695376", + "project_id": "optical-414516", + "private_key_id": "73ecf63211986853f051725e27c78af1f87f420f", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCjF+i1Krdv3HMM\nK+aKlSUV2O4RH3UPf/WXdH0GSFnz7Y5km0Za3g9DqHYQ2lBaDaaGV/27OdXf7u5E\n9tLYOajy11x1njs0g2tLzAqXu6tLwvCQWDT/lpxN0Edg7mGtFP7/jhlystEJXHwc\nyzFkKOBCKD0n83mkbpgOUnEcpXLyk2pbwet5F5cQH2R5/I+6IpTMdsdIrs3UISdA\nyYDjpYYPNYuHZ59iX4S2ILD/sjuCDTUGed1stooqU1YevJhq5gs7DZ7QM80DGaP2\ngsKxHtntajLKz7uT8K45QXTj1iaH8+jDR70SE5ohWnHudj6JnalLaczjMoFqTML3\nFJRfy4XnAgMBAAECggEACr5an4M6sJKP55eFaSVNsJno24A8KLMQCBPt3889/GIZ\nP7UKK5aLizRFNsioLg55JPTAllKcgqhi0hsvpNEY49+zoQc1NOPgBQ8MtusaFnRh\n1CMODAotWLjlgtP2jj10Vib+Xn5mDnJPtgvdQJEAGODoCmzbsK0dCiXR0AZxbduU\n5H4Nq8aPRxgnGlQYk2NXoD8DnmmA4n+mP+LpNHqg65jQFkfbGvASPiGGNu86+lG0\ny1hUe3QTji25UxugVh6Bdd0YNiffBdT8397yLFkefaYFrVg957mr3nwapYYV5dLz\nnkVcC+62Du3r+aFnmK5X0PaT88p+zJ9Svr6eoqWHxQKBgQDMiy/0azeO4yK++L7I\n+xyPLLxtJcfYOdVpoImhk06lEBF5vEmgQkRhGtjw/hug3wgConKQfkBYTfEzvSg9\nCTb5K2VR8gS0Yy2mJZyjr1uG9FuZNcjzNoG3iGuyTuCsFLggImKinouBRiYcAKW/\nonHwHhyUqeiAfTdHFFrKLOUnewKBgQDMH0gjmOVWAVuDCLI2xW693jCzDuvdECOX\nTq9tLmkyt0SBK2ku+ztbX9Z63fQQKazswHyaPU4UPHgkw1hmYvfYLJTvWYn600Dv\nB42EZi/UWv89/oDBCKHvR3Qya6TbdRcwtpRhNNGExUPqvpSpCgWdVAUMhNkQn43g\nfaZyLLkZhQKBgQCHPnztyrrIkqyVwxk7ALaIgJbyQGncMZlHpyt5l1IH2BEGAhZY\njZhy8HKDHjGcc/vy6gCAQ8o92QNkJ21ktiQQKr5qqXCs6Wr0kaf5nugQ2p96NVLy\nZZbtiJlVI8xwW57HOJUpW1V4m651E4SEql9V2kMhlRqzYpfbOr0xMvAt4wKBgAL7\nDfWfHPI1kN2H+fo8xdI19WYX+K/AtZRXDM6+V8Dq1jBArweFmadK7fnbVd4KjcDj\nSq6B6Kr4+xQmWhscpNveQsp/9zjXbuJOrO1bBKXIMmP4XG71LlAtLfAyjDS8L/uB\nHweQWuLicoxx/f+96rkSagR1yCmq+spla1HNf2J1AoGBAI3R0mnB3xqHtKW4Jfau\nvc6mKM7LCcIWjfXt+L8J4d74w27SaM40V2BLnMs5dFcS8jJ7q0eBBlgm5uPZKSyb\nDlKupL+2kh4khdyZ7TqGkA5ESTRVml7OchQhvhDq0ak8cNKjebmNavPbqT5QcF48\ntPmATpKBlOEEOwUPgfhZ7F9S\n-----END PRIVATE KEY-----\n", + "client_email": "adobe-ps-api@optical-414516.iam.gserviceaccount.com", + "client_id": "110023521972993629062", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/lor-txt-tmp-bkt%40optical-visual-intelligence.iam.gserviceaccount.com", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/adobe-ps-api%40optical-414516.iam.gserviceaccount.com", "universe_domain": "googleapis.com" -} \ No newline at end of file +} diff --git a/simplified_payload.py b/simplified_payload.py index 3be3a88..44dcaae 100755 --- a/simplified_payload.py +++ b/simplified_payload.py @@ -33,7 +33,7 @@ from gcs_storage import GCSStorage token_manager = AdobeTokenManager(config.ADOBE_CLIENT_ID, config.ADOBE_CLIENT_SECRET) # GCS bucket configuration -GCS_BUCKET_NAME = "lor-txt-tmp-bkt" +GCS_BUCKET_NAME = "lor-txt-tmp-bkt-26" GCS_KEY_PATH = os.path.join(os.path.dirname(__file__), "gcs_key.json") # Initialize GCS storage @@ -70,26 +70,45 @@ def update_text_with_simplified_payload(json_path: str, psd_path: str): timestamp = int(time.time()) remote_path = f"adobe_ps/{timestamp}_{os.path.basename(psd_path)}" output_path = f"adobe_ps/output_{timestamp}_{os.path.basename(psd_path)}" - + # Upload file upload_result = gcs_storage.upload_file(psd_path, remote_path) - + if not upload_result.get("success"): logger.error(f"Upload failed: {upload_result.get('message')}") return {"success": False, "message": f"Upload failed: {upload_result.get('message')}"} - + # Get signed URLs input_url = upload_result.get("download_url") output_url = gcs_storage.get_signed_url( - output_path, - action="write", + output_path, + action="write", content_type="image/vnd.adobe.photoshop" ) - + logger.info(f"File uploaded to GCS and URLs generated") except Exception as e: logger.error(f"Error with GCS: {e}") return {"success": False, "message": f"Error with GCS: {e}"} + + # Upload any custom fonts found in the local fonts/ directory + font_urls = [] + fonts_dir = os.path.join(os.path.dirname(__file__), "fonts") + if os.path.isdir(fonts_dir): + for font_file in os.listdir(fonts_dir): + if font_file.lower().endswith(('.ttf', '.otf', '.ttc', '.woff', '.woff2')): + font_path = os.path.join(fonts_dir, font_file) + font_remote = f"adobe_ps/fonts/{timestamp}_{font_file}" + try: + font_upload = gcs_storage.upload_file(font_path, font_remote) + if font_upload.get("success"): + font_url = font_upload.get("download_url") + font_urls.append(font_url) + logger.info(f"Uploaded font '{font_file}' to GCS") + else: + logger.warning(f"Failed to upload font '{font_file}': {font_upload.get('message')}") + except Exception as fe: + logger.warning(f"Error uploading font '{font_file}': {fe}") # Get authentication token try: @@ -102,63 +121,30 @@ def update_text_with_simplified_payload(json_path: str, psd_path: str): text_updates = [] for layer in json_data.get('textLayers', []): if layer.get('updatedText') and layer.get('text') != layer.get('updatedText'): - # Get font information if available + text_obj = { + "contents": layer.get('updatedText') + } + + # Include characterStyles with only fontPostScriptName to preserve the font style_info = layer.get('styleInfo', {}) font_name = style_info.get('font') if style_info else None - font_size = style_info.get('size') if style_info else None - - # Create base layer update with name + if font_name: + text_obj["characterStyles"] = [{"fontPostScriptName": font_name}] + logger.info(f"Requesting font '{font_name}' for layer '{layer.get('name')}'") + layer_update = { - "name": layer.get('name', '') + "name": layer.get('name', ''), + "text": text_obj } - - # Create text object with content - text_obj = { - "content": layer.get('updatedText') - } - - # Add proper characterStyles if available - this is the correct way per API spec - if font_name and font_size: - # Convert font size to points - Adobe API expects points (pixels/72) - font_size_pts = float(font_size) / 72.0 - - # Add characterStyles array with the font info - text_obj["characterStyles"] = [ - { - "fontPostScriptName": font_name, - "size": font_size_pts - } - ] - - # Add paragraph style too - text_obj["paragraphStyles"] = [ - { - "alignment": style_info.get('alignment', 'left') - } - ] - - logger.info(f"Added font '{font_name}' size {font_size_pts}pts (converted from {font_size}px) for layer '{layer.get('name')}'") - - # Add the text object to the layer update - layer_update["text"] = text_obj - + + logger.info(f"Updating layer '{layer.get('name')}': '{layer.get('text')[:40]}...' -> '{layer.get('updatedText')[:40]}...'") text_updates.append(layer_update) if not text_updates: logger.info("No text changes needed") return {"success": True, "message": "No text changes needed"} - # Get the global font from the first text layer if available - global_font = None - if json_data.get('textLayers'): - for layer in json_data.get('textLayers'): - style_info = layer.get('styleInfo', {}) - if style_info and style_info.get('font'): - global_font = style_info.get('font') - break - - # Create simplified payload - according to API documentation - # We'll only use the options that are explicitly allowed in the schema + # Create payload per Adobe API documentation payload = { "inputs": [ { @@ -173,17 +159,17 @@ def update_text_with_simplified_payload(json_path: str, psd_path: str): { "storage": "external", "href": output_url, - "type": "image/vnd.adobe.photoshop" + "type": "vnd.adobe.photoshop" } ] } - - # Add font management options - if global_font: - # Try adding font options according to the API schema - payload["options"]["globalFont"] = global_font - payload["options"]["manageMissingFonts"] = "useDefault" - logger.info(f"Using global font: {global_font}") + + # Add custom fonts if any were uploaded + if font_urls: + payload["options"]["fonts"] = [ + {"storage": "external", "href": url} for url in font_urls + ] + logger.info(f"Added {len(font_urls)} custom font(s) to payload") # Set up request headers headers = { diff --git a/updated_text_payload.py b/updated_text_payload.py index 0f25089..62a8f8b 100755 --- a/updated_text_payload.py +++ b/updated_text_payload.py @@ -35,7 +35,7 @@ from gcs_storage import GCSStorage token_manager = AdobeTokenManager(config.ADOBE_CLIENT_ID, config.ADOBE_CLIENT_SECRET) # GCS bucket configuration -GCS_BUCKET_NAME = "lor-txt-tmp-bkt" +GCS_BUCKET_NAME = "lor-txt-tmp-bkt-26" GCS_KEY_PATH = os.path.join(os.path.dirname(__file__), "gcs_key.json") # Initialize GCS storage