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 0000000..1472546 Binary files /dev/null and b/fonts/FuturaPT-Demi.otf differ 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