Add MEMBERSHIP, the club's own 1980s VHS advert

cut.py --cut ad: a plummy announcer (one gpt-audio read per line, ad.sh)
sells St. Mordred's over the creepiest takes with yellow 80s supers, a
PLAY/STOP VCR read-out, two testimonials from the real lines and the small
print read at speed, until the game's umpire says "Quiet." and the jingle
dies. A VHS grade (chroma smear, scanlines, tape wobble) is the cut's own.
gen.py learns a voice command; one new silent Doris take.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
wuwiandClaude Opus 5.5 committed 2026-10-05 11:46:31 +02:00
1 parent da09b55525
commit 806a739c6f
7 files changed
+170 -11

No files matched your search

+24 -5
View File
@@ -4,6 +4,7 @@
uv run --with requests --with pillow python tools/trailer/gen.py still <name> "<prompt>" [ref.png ...]
uv run --with requests --with pillow python tools/trailer/gen.py clip <name> <model> <secs> <first.png> "<prompt>" [last.png]
uv run --with requests --with pillow python tools/trailer/gen.py music <name> "<prompt>"
uv run --with requests --with pillow python tools/trailer/gen.py voice <name> "<style>" "<script>" [voice]
Everything lands in promo/trailer/ (gitignored). Each call also writes <name>.json beside its
output with the exact request, so a take can be re-rolled or compared later.
@@ -85,9 +86,9 @@ def clip(name, model, secs, first, prompt, last=None, res='1080p', audio=True):
print(out, len(v.content), 'bytes', f'{time.time() - t0:.0f}s', flush=True)
def music(name, prompt, model='google/lyria-3-pro-preview'):
req = {'model': model, 'messages': [{'role': 'user', 'content': prompt}],
'modalities': ['audio', 'text'], 'stream': True}
def music(name, prompt, model='google/lyria-3-pro-preview', messages=None, **extra):
req = {'model': model, 'messages': messages or [{'role': 'user', 'content': prompt}],
'modalities': ['audio', 'text'], 'stream': True, **extra}
r = requests.post(f'{API}/chat/completions', headers=H(), json=req, stream=True, timeout=900)
if r.status_code != 200:
sys.exit(f'{name}: HTTP {r.status_code} {r.text[:600]}')
@@ -106,18 +107,36 @@ def music(name, prompt, model='google/lyria-3-pro-preview'):
text.append(d['content'])
if not chunks:
sys.exit(f'{name}: no audio. text: {"".join(text)[:400]}')
out = os.path.join(OUT, 'audio', f'{name}.{fmt}')
open(out, 'wb').write(base64.b64decode(''.join(chunks)))
data = base64.b64decode(''.join(chunks))
if (extra.get('audio') or {}).get('format') == 'pcm16': # streamed speech is raw 24 kHz mono
import wave
out = os.path.join(OUT, 'audio', f'{name}.wav')
with wave.open(out, 'wb') as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(24000); w.writeframes(data)
else:
out = os.path.join(OUT, 'audio', f'{name}.{fmt}')
open(out, 'wb').write(data)
note(out, req)
print(out, ''.join(text)[:300], flush=True)
def voice(name, style, script, voice_id='ballad', model='openai/gpt-audio'):
"""A read of SCRIPT, word for word, in STYLE (an announcer, a whisper...)."""
music(name, None, model, messages=[
{'role': 'system', 'content': 'You are a professional voice actor in a recording booth. Perform the '
'user message as a script: say exactly those words, in that order, nothing added, nothing left out, '
'no greeting, no comment. Style: ' + style},
{'role': 'user', 'content': script}], audio={'voice': voice_id, 'format': 'pcm16'})
if __name__ == '__main__':
a = sys.argv[1:]
if a[:1] == ['still']:
still(a[1], a[2], a[3:])
elif a[:1] == ['clip']:
clip(a[1], a[2], a[3], a[4], a[5], a[6] if len(a) > 6 else None)
elif a[:1] == ['voice']:
voice(a[1], a[2], a[3], *a[4:5])
elif a[:1] == ['music']:
music(a[1], a[2], *a[3:4])
else: