[object Object]

← back to Greenland Onboard

auto-save: 2026-07-13T08:53:46 (1 files) — scripts/make-cork-reels.py

180933436a75b0b3c5ff079d7a9d846251695bf3 · 2026-07-13 08:53:54 -0700 · Steve Abrams

Files touched

Diff

commit 180933436a75b0b3c5ff079d7a9d846251695bf3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 13 08:53:54 2026 -0700

    auto-save: 2026-07-13T08:53:46 (1 files) — scripts/make-cork-reels.py
---
 scripts/make-cork-reels.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/scripts/make-cork-reels.py b/scripts/make-cork-reels.py
new file mode 100644
index 0000000..2e238e4
--- /dev/null
+++ b/scripts/make-cork-reels.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python3
+# Build one branded 9:16 reel per cork COASTAL-CITY pattern from local cork images.
+# Local only ($0): ImageMagick per-frame branding + ffmpeg xfade slideshow. No posting.
+import json, re, subprocess, os, collections, math, shutil
+ROOT = os.path.expanduser('~/Projects/greenland-onboard')
+IMG = f'{ROOT}/public/img'
+OUT = os.path.expanduser('~/Videos/cork-reels')
+TMP = '/tmp/cork-reel-frames'
+os.makedirs(OUT, exist_ok=True)
+DB = 'postgresql:///dw_unified?host=/tmp&user=stevestudio2'
+FONT = '/System/Library/Fonts/Supplemental/Georgia.ttf'
+FONTB = '/System/Library/Fonts/Supplemental/Georgia Bold.ttf'
+CITY = {'S300':'Montauk','H171':'Nantucket','G0174':'Newport','S176':'Sag Harbor','G0181':'Cape May',
+        'G0155':'Palm Beach','G0111':'Kiawah','G0110':'Hilton Head','G0182':'Chatham','G0154':'Southampton',
+        'G0148':'Amagansett','H192':"Martha's Vineyard"}
+MAXF = 15  # frames per reel (evenly sampled if more)
+DUR, XF = 2.5, 0.5
+
+def series(m):
+    x = re.match(r'^[A-Za-z]+[0-9]{2,4}', (m or '').strip()); return x.group(0) if x else ''
+def tc(s): return re.sub(r'\w\S*', lambda w: w.group(0)[:1].upper()+w.group(0)[1:].lower(), (s or '').strip())
+
+rows = json.loads(subprocess.check_output(['/opt/homebrew/opt/postgresql@14/bin/psql', DB, '-tAc',
+  "select coalesce(json_agg(json_build_object('dw',dw_sku,'mfr',mfr_sku,'color',color_name) order by dw_sku)::text,'[]') from greenland_catalog"]).decode().strip())
+
+by = collections.OrderedDict()
+for r in rows:
+    c = CITY.get(series(r['mfr']))
+    if not c: continue
+    img = f"{IMG}/{r['dw']}.jpg"
+    if os.path.exists(img): by.setdefault(c, []).append((img, tc(r['color'])))
+
+def sample(lst, n):
+    return lst if len(lst) <= n else [lst[round(i*(len(lst)-1)/(n-1))] for i in range(n)]
+
+def brand(src, dst, city, color):
+    subprocess.run(['magick', src, '-resize', '1080x1920^', '-gravity', 'center', '-extent', '1080x1920',
+        # bottom scrim for legibility
+        '(', '-size', '1080x620', 'gradient:none-black', ')', '-gravity', 'south', '-compose', 'over', '-composite',
+        # top scrim
+        '(', '-size', '1080x360', 'gradient:black-none', ')', '-gravity', 'north', '-compose', 'over', '-composite',
+        '-font', FONTB, '-fill', 'white', '-gravity', 'north', '-pointsize', '96', '-annotate', '+0+120', city,
+        '-font', FONT, '-fill', '#e8dcc8', '-pointsize', '40', '-annotate', '+0+240', 'Cork Wallcovering',
+        '-font', FONT, '-fill', 'white', '-gravity', 'south', '-pointsize', '58', '-annotate', '+0+150', color,
+        '-font', FONTB, '-fill', '#c9a86a', '-gravity', 'south', '-pointsize', '30', '-annotate', '+0+70',
+        'PHILLIPE ROMANO', '-quality', '92', dst], check=True)
+
+made = []
+for city, items in by.items():
+    frames = sample(items, MAXF)
+    fdir = f'{TMP}/{re.sub(r"[^a-z0-9]","-",city.lower())}'
+    shutil.rmtree(fdir, ignore_errors=True); os.makedirs(fdir)
+    prepped = []
+    for i, (src, color) in enumerate(frames):
+        dst = f'{fdir}/f{i:03d}.jpg'; brand(src, dst, city, color); prepped.append(dst)
+    # ffmpeg xfade chain
+    inp, filt, off = [], '', DUR - XF
+    for i, p in enumerate(prepped):
+        inp += ['-loop', '1', '-t', str(DUR), '-i', p]
+    for i in range(1, len(prepped)):
+        prev = '0' if i == 1 else f'v{i-1}'
+        filt += f'[{prev}][{i}]xfade=transition=fade:duration={XF}:offset={off:.2f}[v{i}];'
+        off += DUR - XF
+    last = f'v{len(prepped)-1}' if len(prepped) > 1 else '0'
+    filt += f'[{last}]format=yuv420p,fps=30[out]'
+    outdir = f'{OUT}/{re.sub(r"[^A-Za-z0-9]","_",city)}'; os.makedirs(outdir, exist_ok=True)
+    outfile = f'{outdir}/reel-9x16.mp4'
+    subprocess.run(['ffmpeg', '-y', *inp, '-filter_complex', filt, '-map', '[out]',
+        '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '22', '-preset', 'medium', '-t', '59', outfile],
+        check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+    dur = round((len(prepped)*(DUR-XF))+XF, 1)
+    made.append((city, len(prepped), outfile, dur))
+    print(f'  ✓ {city}: {len(prepped)} frames → {outfile} (~{dur}s)')
+
+print(f'\nDONE {len(made)} cork reels → {OUT}')
+json.dump([{'city':c,'frames':f,'file':o,'seconds':d} for c,f,o,d in made], open(f'{OUT}/manifest.json','w'), indent=1)

← 3fd488b auto-save: 2026-07-13T01:52:00 (1 files) — scripts/rerule-ma  ·  back to Greenland Onboard  ·  trim trailing whitespace in mfrModel (scraper + existing ful fcfbcf5 →