-
Notifications
You must be signed in to change notification settings - Fork 934
Description
import os, subprocess, random
from PIL import Image, ImageDraw, ImageFont, ImageFilter
--------- ΡΥΘΜΙΣΕΙΣ ---------
INPUT_SUBJECT = "horror_girl_logo_PRINTED_move_480x854_10s.mp4" # το 10s σου
OUT_VIDEO = "final_10s_athens_riot.mp4"
W, H = 1080, 1920
DUR, FPS = 10, 30
colorkey για να “κόψει” μαύρο φόντο (αν χρειαστεί, παίξε με αυτά)
COLORKEY_HEX = "0x000000" # key κοντά σε μαύρο
SIMILARITY = 0.25 # 0..1 (μεγαλύτερο = πιο επιθετικό key)
BLEND = 0.05 # 0..1 (μαλάκωμα ακμών alpha)
zoom προς το τέλος (0.0 = καθόλου, 0.18 ≈ +18% μέγεθος στο τέλος)
SUBJ_ZOOM_END = 0.18
ελαφρά ανύψωση προς το τέλος (σε px) για να “πάει” σε κοντινό πρόσωπο
SUBJ_Y_LIFT = 120
από ποιο δευτ. “μπαίνει” το ροζ φως από αριστερά (fade-in)
PINK_LIGHT_FADE_START = 6
PINK_LIGHT_FADE_DUR = 4
--------- ΑΡΧΕΙΑ ΠΟΥ ΘΑ ΦΤΙΑΞΟΥΜΕ ---------
BG_PNG = "athens_graffiti_bg.png"
SMOKE_GRAY_PNG = "smoke_gray.png"
SMOKE_PINK_PNG = "smoke_pink.png"
FIRE_PNG = "fire_orange.png"
LEFT_LIGHT_PNG = "left_pink_light.png"
BG_VID = "bg_10s.mp4"
--------- ΦΟΝΤΟ ΑΘΗΝΑ + GRAFFITI ---------
def make_bg():
img = Image.new("RGBA", (W, H), (6,8,16,255))
d = ImageDraw.Draw(img)
# night gradient
for y in range(H):
t = y / H
r = int(12 + 8 * (1 - t))
g = int(16 + 10 * (1 - t))
b = int(30 + 38 * (1 - t))
d.line([(0, y), (W, y)], fill=(r,g,b,255))
# skyline
random.seed(33)
ground_y = int(H*0.58)
for _ in range(46):
bw = random.randint(44, 150)
bh = random.randint(140, 460)
x = random.randint(-40, W-20)
y = ground_y - bh + random.randint(-50, 50)
d.rectangle([x, y, x+bw, ground_y], fill=(10,12,24,255))
# windows
for _ in range(1400):
bw = random.randint(6, 11)
bh = random.randint(6, 11)
x = random.randint(0, W-1)
y = random.randint(int(H*0.20), ground_y-10)
if random.random() < 0.22:
col = random.choice([(255,230,150,255),(230,245,255,255),(255,185,100,255)])
d.rectangle([x, y, x+bw, y+bh], fill=col)
# wall
wall_top = ground_y + 18
d.rectangle([0, wall_top, W, H], fill=(14,14,16,255))
# font
font = None
for p in ["/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"]:
if os.path.exists(p):
font = p; break
def stamp(text, x, y, size, angle, color):
fnt = ImageFont.truetype(font, size) if font else None
tmp = Image.new("RGBA", (1,1), (0,0,0,0))
td = ImageDraw.Draw(tmp)
bbox= td.textbbox((0,0), text, font=fnt)
tw, th = bbox[2]-bbox[0], bbox[3]-bbox[1]
pad = 10
tile = Image.new("RGBA", (tw+2*pad, th+2*pad), (0,0,0,0))
tdraw= ImageDraw.Draw(tile)
tdraw.text((pad, pad), text, font=fnt, fill=color)
tile = tile.rotate(angle, expand=True, resample=Image.Resampling.BICUBIC)
img.alpha_composite(tile, (x, y))
# graffiti bombs SALIMARIA 013
for _ in range(220):
size = random.randint(30, 160)
angle = random.randint(-20, 20)
col = random.choice([(255, 90,160,255),(255,120, 40,255),(255,255,255,255),
(240, 60,120,255),(180,255,120,255)])
x = random.randint(-70, W-70)
y = random.randint(wall_top-50, H-60)
stamp("SALIMARIA 013", x, y, size, angle, col)
img.save(BG_PNG, "PNG")
--------- SMOKE / FIRE / LIGHT PNGs ---------
def make_smoke(path, base_color, blobs=30, max_r=320, alpha=150, y_min=0.45, y_max=1.05):
img = Image.new("RGBA", (W, H), (0,0,0,0))
dr = ImageDraw.Draw(img)
for _ in range(blobs):
r = random.randint(100, max_r)
cx = random.randint(-120, W+120)
cy = random.randint(int(Hy_min), int(Hy_max))
for k in range(6,0,-1):
rr = int(r*(1+0.16k))
aa = max(25, int(alpha(k/7)))
dr.ellipse([cx-rr, cy-rr, cx+rr, cy+rr], fill=(base_color[0],base_color[1],base_color[2],aa))
img = img.filter(ImageFilter.GaussianBlur(radius=32))
img.save(path, "PNG")
def make_fire(path, base_color=(255,120,40), blobs=28, max_r=240):
img = Image.new("RGBA", (W, H), (0,0,0,0))
dr = ImageDraw.Draw(img)
for _ in range(blobs):
r = random.randint(80, max_r)
cx = random.randint(-60, W+60)
cy = random.randint(int(H0.70), int(H0.95))
for k in range(6,0,-1):
rr = int(r*(1+0.18k))
aa = max(30, int(180(k/7)))
dr.ellipse([cx-rr, cy-rr, cx+rr, cy+rr], fill=(base_color[0],base_color[1],base_color[2],aa))
img = img.filter(ImageFilter.GaussianBlur(radius=26))
img.save(path, "PNG")
def make_left_light(path):
grad = Image.new("RGBA", (W, H), (0,0,0,0))
gd = ImageDraw.Draw(grad)
for x in range(int(W0.45)):
t = 1 - x/(W0.45)
a = int(200 * (t**1.6))
gd.line([(x,0),(x,H)], fill=(255,100,180,a))
grad = grad.filter(ImageFilter.GaussianBlur(radius=22))
grad.save(path, "PNG")
--------- RUN HELPERS ---------
def run_ffmpeg(cmd):
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if p.returncode != 0:
print(p.stderr)
raise RuntimeError("ffmpeg failed")
def main():
if not os.path.exists(INPUT_SUBJECT):
raise FileNotFoundError(f"Δεν βρέθηκε το subject video: {INPUT_SUBJECT}")
make_bg()
make_smoke(SMOKE_GRAY_PNG, (180,180,185), blobs=34, max_r=340, alpha=150, y_min=0.40, y_max=1.02)
make_smoke(SMOKE_PINK_PNG, (255,105,180), blobs=26, max_r=300, alpha=160, y_min=0.50, y_max=1.05)
make_fire(FIRE_PNG, (255,120,40), blobs=28, max_r=240)
make_left_light(LEFT_LIGHT_PNG)
# 10s background video
run_ffmpeg([
"ffmpeg","-y",
"-loop","1","-t", str(DUR), "-i", BG_PNG,
"-vf", f"scale={W}:{H},format=yuv420p",
"-r", str(FPS), "-pix_fmt","yuv420p",
BG_VID
])
# Compose όλα μαζί (χωρίς glitch). Zoom στο subject & ροζ φως από αριστερά στο τέλος.
filtergraph = (
"[0:v]format=rgba[bg];"
"[1:v]format=rgba,scale=1080:1920[sgray];"
"[bg][sgray]overlay=x='20*sin(0.3*t)':y='H-900-30*t':eval=frame:shortest=1[lay1];"
"[2:v]format=rgba,scale=1080:1920[spink];"
"[lay1][spink]overlay=x='-120+40*t':y='H-800-25*t':eval=frame:shortest=1[lay2];"
"[3:v]format=rgba,scale=1080:1920,fps=30[fire];"
"[fire]eq=brightness='0.06*sin(15*t)'[firef];"
"[lay2][firef]overlay=x='0':y='0':shortest=1[lay3];"
f"[4:v]format=rgba,scale=1080:1920,fade=t=in:st={PINK_LIGHT_FADE_START}:d={PINK_LIGHT_FADE_DUR}:alpha=1[leftlight];"
"[lay3][leftlight]overlay=x='0':y='0':shortest=1[base];"
# Subject zoom over time + ελαφρά άνοδος προς τα πάνω (για κοντινό)
f"[5:v]scale='trunc(iw*(1+{SUBJ_ZOOM_END}*(t/{DUR}))/2)*2':'trunc(ih*(1+{SUBJ_ZOOM_END}*(t/{DUR}))/2)*2':eval=frame,"
f"format=rgba,colorkey={COLORKEY_HEX}:{SIMILARITY}:{BLEND},boxblur=2:2:cr=1:ar=1[subj];"
f"[base][subj]overlay=x='(W-w)/2':y='(H-h)/2-{SUBJ_Y_LIFT}*(t/{DUR})':shortest=1[comp];"
"[comp]format=yuv420p[v]"
)
run_ffmpeg([
"ffmpeg","-y",
"-i", BG_VID,
"-stream_loop","-1","-t", str(DUR), "-i", SMOKE_GRAY_PNG,
"-stream_loop","-1","-t", str(DUR), "-i", SMOKE_PINK_PNG,
"-stream_loop","-1","-t", str(DUR), "-i", FIRE_PNG,
"-stream_loop","-1","-t", str(DUR), "-i", LEFT_LIGHT_PNG,
"-i", INPUT_SUBJECT,
"-filter_complex", filtergraph,
"-map","[v]","-t", str(DUR), "-r", str(FPS), "-pix_fmt","yuv420p",
"-c:v","libx264","-crf","20","-preset","veryfast",
OUT_VIDEO
])
print(f"\n✅ Exported: {OUT_VIDEO}")
if name == "main":
main()