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:
1 parent
da09b55525
commit
806a739c6f
7 files changed
+170
-11
No files matched your search
+24
-5
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user