# 静止画から背景用の映像を作る(ゆっくり寄る)と、早送りの寄りの映像を作る import subprocess, sys, os from PIL import Image W, H, FPS = 1920, 1080, 30 BG = (245, 245, 240) def pipe(out, n, frame_fn): p = subprocess.Popen(['ffmpeg','-v','error','-y','-f','rawvideo','-pix_fmt','rgb24','-s',f'{W}x{H}','-r',str(FPS),'-i','-', '-c:v','libx264','-pix_fmt','yuv420p','-crf','18','-preset','veryfast',out], stdin=subprocess.PIPE) for i in range(n): p.stdin.write(frame_fn(i).tobytes()) p.stdin.close(); p.wait() def place(img, box_w, box_h, cx, cy, crop): """crop=(x,y,w,h) を box に収めて、中心 (cx,cy) に置いた 1920x1080 の絵""" x, y, w, h = crop c = img.crop((int(x), int(y), int(x + w), int(y + h))) s = min(box_w / c.width, box_h / c.height) c = c.resize((max(1, int(c.width * s)), max(1, int(c.height * s))), Image.LANCZOS) f = Image.new('RGB', (W, H), BG); f.paste(c, (int(cx - c.width / 2), int(cy - c.height / 2))) return f def still(src, out, sec=20, z0=1.0, z1=1.12, fill=True): img = Image.open(src).convert('RGB') n = int(sec * FPS) def fr(i): z = z0 + (z1 - z0) * i / n if fill: # 画面いっぱいに(はみ出しは切る) s = max(W / img.width, H / img.height) * z else: s = min(1500 / img.width, 800 / img.height) * z r = img.resize((int(img.width * s), int(img.height * s)), Image.LANCZOS) f = Image.new('RGB', (W, H), BG); f.paste(r, ((W - r.width) // 2, (H - r.height) // 2 - (0 if fill else 40))) return f pipe(out, n, fr) def ff_zoom(out, shots, per=0.55, tail=2.5): """shots=[(画像, 寄る所の中心x, y, 寄り始めの幅, 寄り終わりの幅)]。1つずつ寄りながら、ぱっと切り替える""" imgs = {} frames = [] for path, cx, cy, w0, w1 in shots: if path not in imgs: o = Image.open(path).convert('RGB'); P = 500 pad = Image.new('RGB', (o.width + 2 * P, o.height + 2 * P), BG); pad.paste(o, (P, P)); imgs[path] = pad n = int(per * FPS) for i in range(n): w = w0 + (w1 - w0) * (i / n) h = w * 9 / 16 frames.append((path, (cx + 500 - w / 2, cy + 500 - h / 2, w, h))) last = shots[-1] for i in range(int(tail * FPS)): frames.append((last[0], (last[1] + 500 - last[3] / 2, last[2] + 500 - last[3] * 9 / 32, last[3], last[3] * 9 / 16))) pipe(out, len(frames), lambda k: place(imgs[frames[k][0]], 1500, 844, W / 2, H / 2 - 30, frames[k][1])) if __name__ == '__main__': A, B = 'src/oshiro_first.png', 'src/oshiro_final.png' ff_zoom('src/ff_oshiro.mp4', [ (A, 222, 85, 330, 230), (B, 1060 - 850, 85, 330, 230), # 顔(目) (A, 222, 190, 520, 400), (B, 210, 190, 520, 400), # 肩 (A, 222, 150, 300, 220), (B, 210, 150, 300, 220), # 首と Vネック (A, 640, 140, 360, 260), (B, 1480 - 850, 140, 360, 260), # 横の首 (A, 425, 390, 1500, 1420), (B, 425, 390, 1500, 1420), # 全体 ]) sys.exit(0) if '--ff' in sys.argv else None still('src/oshiro_first.png', 'src/bg_oshiro_first.mp4', fill=False) still('src/hakobu_before.png', 'src/bg_hakobu_before.mp4', fill=False) still('src/sleeve_before_55.png', 'src/bg_sleeve55.mp4', fill=False) still('src/LOG496_06_nurse_poses.png', 'src/bg_poses.mp4', fill=False) print('ok')