← 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
A scripts/make-cork-reels.py
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 →