šŸ“„ generate_cover.py 5,646 bytes Today 02:32 šŸ“‹ Raw

!/usr/bin/env python3

"""
generate_cover.py — Generate Imagen 4.0 blog cover images for HoffDesk blog.

Usage:
python3 generate_cover.py "" "<excerpt>"</p> <p>Dependencies: requests, Pillow (python3 -m pip install --break-system-packages requests Pillow)<br> Auth: Requires VERTEX_AI_KEY and VERTEX_AI_PROJECT env vars.<br> """</p> <p>import os<br> import sys<br> import json<br> import base64<br> import requests<br> from PIL import Image<br> from io import BytesIO</p> <h1 id="config">── Config ───────────────────────────────────────────────────────────────────</h1> <p>API_KEY = os.environ.get("VERTEX_AI_KEY")<br> PROJECT = os.environ.get("VERTEX_AI_PROJECT", "gen-lang-client-0081729505")<br> LOCATION = "us-central1"<br> PUBLISHER = "google"<br> MODEL = "imagen-4.0-generate-001"<br> OUTPUT_DIR = os.environ.get(<br> "COVER_OUTPUT_DIR",<br> "/home/hoffmann_admin/hoffdesk/blog/static/images/posts/"<br> )<br> MAX_WIDTH = 800<br> JPEG_QUALITY = 85<br> SAMPLE_COUNT = 1</p> <h1 id="templates">── Templates ────────────────────────────────────────────────────────────────</h1> <p>BASE_PROMPT = (<br> "Editorial magazine illustration style, cinematic composition, "<br> "high contrast, premium tech magazine aesthetic, clean digital art. "<br> "No text, no logos, no watermarks, no brand names, no written words, "<br> "no typography, no magazine mastheads, no labels anywhere in the image."<br> )</p> <p>def build_prompt(title: str, excerpt: str) -> str:<br> """Build a visual prompt from blog metadata."""<br> concept = f"{title}: {excerpt}" if excerpt else title<br> # Strip very long excerpts<br> if len(concept) > 200:<br> concept = concept[:197] + "..."<br> return (<br> f"{concept}. "<br> f"Dark background, neon accent lighting, dramatic atmosphere. "<br> f"{BASE_PROMPT}"<br> )</p> <h1 id="generation">── Generation ───────────────────────────────────────────────────────────────</h1> <p>def generate_image(prompt: str, slug: str) -> str | None:<br> """Generate a cover image via Imagen 4.0, save as WebP + JPEG, return path."""<br> if not API_KEY:<br> print("āŒ VERTEX_AI_KEY not set. Export it or set in .bashrc", file=sys.stderr)<br> return None</p> <div class="highlight"><pre><span></span><code><span class="n">url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span> <span class="w"> </span><span class="n">f</span><span class="s2">"https://{LOCATION}-aiplatform.googleapis.com/v1/"</span> <span class="w"> </span><span class="n">f</span><span class="s2">"projects/{PROJECT}/locations/{LOCATION}/"</span> <span class="w"> </span><span class="n">f</span><span class="s2">"publishers/{PUBLISHER}/models/{MODEL}:predict"</span> <span class="p">)</span> <span class="n">headers</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="s2">"x-goog-api-key"</span><span class="p">:</span><span class="w"> </span><span class="n">API_KEY</span><span class="p">,</span> <span class="w"> </span><span class="s2">"Content-Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"application/json"</span><span class="p">,</span> <span class="p">}</span> <span class="n">payload</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="s2">"instances"</span><span class="p">:</span><span class="w"> </span><span class="p">[{</span><span class="s2">"prompt"</span><span class="p">:</span><span class="w"> </span><span class="n">prompt</span><span class="p">}],</span> <span class="w"> </span><span class="s2">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="s2">"sampleCount"</span><span class="p">:</span><span class="w"> </span><span class="n">SAMPLE_COUNT</span><span class="p">,</span> <span class="w"> </span><span class="s2">"aspectRatio"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4:3"</span><span class="p">,</span> <span class="w"> </span><span class="p">},</span> <span class="p">}</span> <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s2">" Generating image for: {slug}"</span><span class="p">)</span> <span class="n">resp</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">requests</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"> </span><span class="n">headers</span><span class="o">=</span><span class="n">headers</span><span class="p">,</span><span class="w"> </span><span class="n">json</span><span class="o">=</span><span class="n">payload</span><span class="p">,</span><span class="w"> </span><span class="n">timeout</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span> <span class="k">if</span><span class="w"> </span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">200</span><span class="p">:</span> <span class="w"> </span><span class="n">err</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"error"</span><span class="p">,</span><span class="w"> </span><span class="p">{})</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"message"</span><span class="p">,</span><span class="w"> </span><span class="n">resp</span><span class="o">.</span><span class="n">text</span><span class="p">[:</span><span class="mi">200</span><span class="p">])</span> <span class="w"> </span><span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s2">"āŒ API error ({resp.status_code}): {err}"</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">None</span> <span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()</span> <span class="k">if</span><span class="w"> </span><span class="s2">"predictions"</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="ow">in</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="ow">or</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="n">data</span><span class="p">[</span><span class="s2">"predictions"</span><span class="p">]:</span> <span class="w"> </span><span class="nb">print</span><span class="p">(</span><span class="s2">"āŒ No predictions returned"</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">None</span> <span class="n">img_b64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">data</span><span class="p">[</span><span class="s2">"predictions"</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"bytesBase64Encoded"</span><span class="p">)</span> <span class="k">if</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="n">img_b64</span><span class="p">:</span> <span class="w"> </span><span class="nb">print</span><span class="p">(</span><span class="s2">"āŒ No image data in response"</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">)</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">None</span> <span class="c1"># Decode</span> <span class="n">img_bytes</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">base64</span><span class="o">.</span><span class="n">b64decode</span><span class="p">(</span><span class="n">img_b64</span><span class="p">)</span> <span class="n">img</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">BytesIO</span><span class="p">(</span><span class="n">img_bytes</span><span class="p">))</span> <span class="c1"># Resize</span> <span class="n">new_w</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">min</span><span class="p">(</span><span class="n">MAX_WIDTH</span><span class="p">,</span><span class="w"> </span><span class="n">img</span><span class="o">.</span><span class="n">width</span><span class="p">)</span> <span class="n">new_h</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb nb-Type">int</span><span class="p">(</span><span class="n">img</span><span class="o">.</span><span class="n">height</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="p">(</span><span class="n">new_w</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">img</span><span class="o">.</span><span class="n">width</span><span class="p">))</span> <span class="n">img_resized</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">img</span><span class="o">.</span><span class="n">resize</span><span class="p">((</span><span class="n">new_w</span><span class="p">,</span><span class="w"> </span><span class="n">new_h</span><span class="p">),</span><span class="w"> </span><span class="n">Image</span><span class="o">.</span><span class="n">LANCZOS</span><span class="p">)</span> <span class="c1"># Ensure output dir exists</span> <span class="n">os</span><span class="o">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">OUTPUT_DIR</span><span class="p">,</span><span class="w"> </span><span class="n">exist_ok</span><span class="o">=</span><span class="n">True</span><span class="p">)</span> <span class="c1"># Save JPEG</span> <span class="n">jpg_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUTPUT_DIR</span><span class="p">,</span><span class="w"> </span><span class="n">f</span><span class="s2">"{slug}-cover.jpg"</span><span class="p">)</span> <span class="n">img_resized</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s2">"RGB"</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">jpg_path</span><span class="p">,</span><span class="w"> </span><span class="s2">"JPEG"</span><span class="p">,</span><span class="w"> </span><span class="n">quality</span><span class="o">=</span><span class="n">JPEG_QUALITY</span><span class="p">,</span><span class="w"> </span><span class="n">optimize</span><span class="o">=</span><span class="n">True</span><span class="p">)</span> <span class="c1"># Save WebP (smaller for browsers that support it)</span> <span class="n">webp_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUTPUT_DIR</span><span class="p">,</span><span class="w"> </span><span class="n">f</span><span class="s2">"{slug}-cover.webp"</span><span class="p">)</span> <span class="n">img_resized</span><span class="o">.</span><span class="n">convert</span><span class="p">(</span><span class="s2">"RGB"</span><span class="p">)</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="n">webp_path</span><span class="p">,</span><span class="w"> </span><span class="s2">"WEBP"</span><span class="p">,</span><span class="w"> </span><span class="n">quality</span><span class="o">=</span><span class="n">JPEG_QUALITY</span><span class="p">)</span> <span class="c1"># Save full-res PNG backup</span> <span class="n">png_path</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">OUTPUT_DIR</span><span class="p">,</span><span class="w"> </span><span class="n">f</span><span class="s2">"{slug}-cover.png"</span><span class="p">)</span> <span class="n">with</span><span class="w"> </span><span class="n">open</span><span class="p">(</span><span class="n">png_path</span><span class="p">,</span><span class="w"> </span><span class="s2">"wb"</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">f</span><span class="p">:</span> <span class="w"> </span><span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">img_bytes</span><span class="p">)</span> <span class="n">jpg_size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">jpg_path</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s2">" āœ… JPEG: {jpg_path} ({jpg_size/1024:.0f} KB)"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s2">" āœ… WebP: {webp_path}"</span><span class="p">)</span> <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="s2">" šŸ“¦ PNG backup: {png_path}"</span><span class="p">)</span> <span class="c1"># Return the relative path for use in frontmatter</span> <span class="k">return</span><span class="w"> </span><span class="n">f</span><span class="s2">"/blog/static/images/posts/{slug}-cover.jpg"</span> </code></pre></div> <h1 id="main">── Main ─────────────────────────────────────────────────────────────────────</h1> <p>def main():<br> if len(sys.argv) < 3:<br> print("Usage: python3 generate_cover.py <slug> \"<title>\" [\"<excerpt>\"]", file=sys.stderr)<br> sys.exit(1)</p> <div class="highlight"><pre><span></span><code>slug = sys.argv[1] title = sys.argv[2] excerpt = sys.argv[3] if len(sys.argv) > 3 else "" prompt = build_prompt(title, excerpt) print(f"šŸ“ Prompt ({len(prompt)} chars):") print(f" {prompt[:200]}...") print() cover_path = generate_image(prompt, slug) if cover_path: print(f"\nāœ… Cover generated!") print(f" Frontmatter: cover_image: {cover_path}") else: print("\nāŒ Failed to generate cover", file=sys.stderr) sys.exit(1) </code></pre></div> <p>if <strong>name</strong> == "<strong>main</strong>":<br> main()</p> </div> <div class="mt32"> <a href="/browse/%F0%9F%8E%A8%20Daedalus/scripts">← Back</a> </div> </main> </div> </body> </html>