| Server IP : 198.71.58.22 / Your IP : 216.73.216.72 Web Server : Apache/2.4.62 (AlmaLinux) OpenSSL/3.2.2 System : Linux localhost 5.14.0-570.33.2.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Aug 14 07:37:35 EDT 2025 x86_64 User : root ( 0) PHP Version : 8.3.24 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/html/lesliepobee.com/artifacts/ |
Upload File : |
#!/usr/bin/env python3
"""
Leslie Pobee homepage video generator.
Step 1: Upload image + audio to fal.ai storage
Step 2: Generate lip-sync video (photo + full audio)
Step 3: Generate star intro (text-to-video)
Step 4: Combine with FFmpeg (star intro → crossfade → lip-sync)
"""
import os
import sys
import json
import time
import subprocess
import urllib.request
os.environ['FAL_KEY'] = '05ea151f-8f29-4608-8c55-1080548e8b03:87942e7b7d0313b9824be4af371683ba'
import fal_client
ARTIFACTS = '/var/www/html/lesliepobee.com/artifacts'
IMAGE_PATH = f'{ARTIFACTS}/LeslieSingingArmsOut.jpg'
AUDIO_PATH = f'{ARTIFACTS}/homepageaudio.mp3.wav'
STAR_VIDEO_PATH = f'{ARTIFACTS}/star_intro.mp4'
LIPSYNC_VIDEO_PATH = f'{ARTIFACTS}/lipsync_output.mp4'
FINAL_VIDEO_PATH = f'{ARTIFACTS}/leslie_homepage_video.mp4'
STAR_PROMPT = (
"Stars in a dark, deep space sky slowly streaming and sparkling, "
"cinematic slow motion, dark atmospheric background, ethereal glow, "
"4K quality, smooth motion"
)
LIPSYNC_NEGATIVE = (
"static, whispering mouth, minimal jaw movement, robotic, frozen eyes, "
"distorted face, extra limbs, blurred background"
)
def download(url, dest):
print(f" Downloading to {dest} ...")
urllib.request.urlretrieve(url, dest)
size = os.path.getsize(dest)
print(f" Saved: {size/1024:.1f} KB")
def upload(path, label):
print(f"Uploading {label} ...")
url = fal_client.upload_file(path)
print(f" URL: {url}")
return url
def on_queue_update(update):
if isinstance(update, fal_client.InProgress):
for log in update.logs:
print(f" [log] {log['message']}")
# ── Step 1: Upload files ──────────────────────────────────────────────────────
print("\n=== STEP 1: Upload files to fal.ai storage ===")
image_url = upload(IMAGE_PATH, 'LeslieSingingArmsOut.jpg')
audio_url = upload(AUDIO_PATH, 'homepageaudio.mp3.wav')
# ── Step 2: Lip-sync video ────────────────────────────────────────────────────
print("\n=== STEP 2: Generate lip-sync video (sync-lipsync) ===")
try:
result = fal_client.subscribe(
'fal-ai/sync-lipsync',
arguments={
'video_url': image_url,
'audio_url': audio_url,
'model': 'lipsync-2',
'sync_mode': 'bounce',
},
with_logs=True,
on_queue_update=on_queue_update,
)
lipsync_url = result.get('video', {}).get('url') or result.get('url')
print(f"Lip-sync URL: {lipsync_url}")
download(lipsync_url, LIPSYNC_VIDEO_PATH)
except Exception as e:
print(f"sync-lipsync failed ({e}), trying sadtalker ...")
result = fal_client.subscribe(
'fal-ai/sadtalker',
arguments={
'source_image_url': image_url,
'driven_audio_url': audio_url,
'still_mode': False,
'preprocess': 'crop',
'size': 512,
},
with_logs=True,
on_queue_update=on_queue_update,
)
lipsync_url = result.get('video', {}).get('url') or result.get('url')
print(f"SadTalker URL: {lipsync_url}")
download(lipsync_url, LIPSYNC_VIDEO_PATH)
# ── Step 3: Star intro (text-to-video) ───────────────────────────────────────
print("\n=== STEP 3: Generate star intro (kling text-to-video) ===")
try:
result = fal_client.subscribe(
'fal-ai/kling-video/v1.6/standard/text-to-video',
arguments={
'prompt': STAR_PROMPT,
'duration': '5',
'aspect_ratio': '16:9',
},
with_logs=True,
on_queue_update=on_queue_update,
)
star_url = (result.get('video') or {}).get('url') or result.get('url')
print(f"Star intro URL: {star_url}")
download(star_url, STAR_VIDEO_PATH)
except Exception as e:
print(f"Kling failed ({e}), trying minimax ...")
result = fal_client.subscribe(
'fal-ai/minimax-video/video-01-live',
arguments={
'prompt': STAR_PROMPT,
'prompt_optimizer': True,
},
with_logs=True,
on_queue_update=on_queue_update,
)
star_url = (result.get('video') or {}).get('url') or result.get('url')
print(f"Minimax URL: {star_url}")
download(star_url, STAR_VIDEO_PATH)
# ── Step 4: Combine with FFmpeg ───────────────────────────────────────────────
print("\n=== STEP 4: Combine star intro + lip-sync with FFmpeg ===")
# Get star intro duration
probe = subprocess.run(
['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', STAR_VIDEO_PATH],
capture_output=True, text=True
)
star_duration = float(json.loads(probe.stdout)['format']['duration'])
print(f"Star intro duration: {star_duration:.2f}s")
# Get lipsync duration
probe2 = subprocess.run(
['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_format', LIPSYNC_VIDEO_PATH],
capture_output=True, text=True
)
lipsync_duration = float(json.loads(probe2.stdout)['format']['duration'])
print(f"Lip-sync duration: {lipsync_duration:.2f}s")
CROSSFADE = 1.0 # seconds of overlap crossfade
# Combine: star intro crossfades into lipsync, audio from lipsync plays throughout
# Strategy: use xfade filter for video, amix or adelay for audio
ffmpeg_cmd = [
'ffmpeg', '-y',
'-i', STAR_VIDEO_PATH,
'-i', LIPSYNC_VIDEO_PATH,
'-filter_complex',
(
f'[0:v]scale=1280:720,fps=25[v0];'
f'[1:v]scale=1280:720,fps=25[v1];'
f'[v0][v1]xfade=transition=fade:duration={CROSSFADE}:offset={star_duration - CROSSFADE}[vout];'
f'[1:a]adelay={(int((star_duration - CROSSFADE) * 1000))}|{int((star_duration - CROSSFADE) * 1000)}[aout]'
),
'-map', '[vout]',
'-map', '[aout]',
'-c:v', 'libx264',
'-c:a', 'aac',
'-shortest',
FINAL_VIDEO_PATH,
]
print("Running FFmpeg combine ...")
r = subprocess.run(ffmpeg_cmd, capture_output=True, text=True)
if r.returncode != 0:
print("FFmpeg stderr:", r.stderr[-2000:])
sys.exit(1)
final_size = os.path.getsize(FINAL_VIDEO_PATH)
print(f"\nFinal video: {FINAL_VIDEO_PATH}")
print(f"Size: {final_size / 1024 / 1024:.2f} MB")
print("Done.")