AI 그림 몇 장과 코드로 돈 안 들이고 숏폼 만들기

머리 위 「Lv.25 오리」 이름표, 맞는 순간의 흔들림과 번쩍, 파란 번개. 게임 숏폼의 문법은 편집 앱 없이 Pillow 와 ffmpeg, 대본 JSON 한 장으로도 나온다.

게임 세계관 숏폼에는 정해진 문법이 있다. 캐릭터 머리 위에 「Lv.숫자 닉네임」이 뜨고, 이름이 흰색이면 일반, 빨강이면 PK나 강자, 주황 대괄호면 NPC다. 때리는 순간 화면이 흔들리고 하얗게 번쩍인다. 번개가 떨어지고, 가슴 높이에 반투명 띠 자막이 깔린다.

이걸 흉내 내려면 편집 앱 타임라인에서 손으로 맞추거나 유료 영상 AI 를 쓰는 게 보통이다. 릴스 본편에는 둘 다 쓰지 않았다. 쓴 건 Pillow 와 ffmpeg, 그리고 그림 AI 에게 받은 그림 몇 장이다. 1080×1920 릴스가 파이썬 스크립트 하나에서 나온다.

정지 그림 몇 장을 코드만으로 "게임 화면"처럼 움직일 수 있을까? reel.py 는 135줄이다.

대본 JSON 한 장이 영상 한 편이다

reel.py 는 대본 파일 하나와 출력 경로만 받는다. 대본은 {"shots":[...]} 이고, 장(shot) 하나에 넣을 수 있는 건 이게 전부다.

코드
  img       그림 경로(1080×1350 카드 — 크림 여백 40px 는 잘라 낸다)
  dur       초
  zoom      [시작배율, 끝배율]  (기본 [1.0, 1.06] — 천천히 다가가기)
  tag       게임 이름표 {"text":"Lv.3 혼자 만드는 개발자", "color":"white|red|npc", "at":0.3}
  hit       [초, ...] 그 순간 화면 흔들림 + 하얀 번쩍 + 줌 펀치 + 타격음
  bolt      [초, ...] 그 순간 번개(파랑)
  cap       아래 자막 한 줄

편집 앱의 타임라인 대신 숫자 목록이 있는 셈이다. "몇 초에 한 대 친다"를 마우스로 끌어다 맞추는 게 아니라 hit 배열에 숫자 하나를 넣는다. 고치고 싶으면 숫자를 바꾸고 다시 돌리면 된다. 같은 대본이면 같은 영상이 나온다.

그림은 인스타 카드용 1080×1350 을 그대로 쓴다. 그 크기면 가장자리 여백을 잘라 낸다(설명엔 40px 라고 적었지만 코드는 사방 44px 를 자른다). 세로 화면을 채우는 배경은 따로 그리지 않는다. 같은 그림을 화면 크기로 늘린 뒤 가우시안 블러 40 을 걸고, 거의 검정에 가까운 색과 55% 섞는다. 그 위에 원본 카드를 폭의 96% 크기로 얹고, 장이 끝날 때까지 1.0 배에서 1.06 배로 천천히 다가간다. 정지 그림이 멈춰 보이지 않게 하는 게 이 6% 줌이다.

한 대 칠 때 화면에서 일어나는 일

타격감은 세 가지가 동시에 터지면서 나온다. 흔들림, 번쩍, 줌 펀치. 코드로 보면 이렇다.

코드
            for h in s.get("hit", []) + s.get("bolt", []):
                dt = t - h
                if 0 <= dt < .35:
                    shake = max(shake, 28 * (1 - dt / .35)); flash = max(flash, 1 - dt / .18)
                    z += .08 * (1 - dt / .35)

맞은 순간부터 0.35초 동안 그림이 최대 28px 범위에서 무작위로 흔들리고, 그 폭이 0 으로 줄어든다. 번쩍은 더 짧다. 0.18초 만에 사라지는 흰 판을 프레임 위에 덮는데, 가장 셀 때 불투명도가 160/255 쯤이다. 동시에 배율이 0.08 만큼 튀었다가 돌아온다.

번개는 지그재그 선이다. 화면 위쪽 y=250 에서 출발해 1500 까지, 한 걸음에 가로로 ±90px, 세로로 50~110px 씩 내려간다. 같은 선을 굵기 34, 18, 7 로 세 번 긋고 투명도를 60, 120, 255 로 올리면 파란 빛이 번진 것처럼 보인다. 가운데엔 3px 짜리 흰 심을 넣는다. 0.45초 동안 세 프레임마다 한 번씩 빼 버리면 깜빡이는 번개가 된다.

머리 위 이름표는 78 크기 글자로, 왼쪽 500px 밖에서 미끄러져 들어온다. 감속 곡선을 걸어 처음엔 빠르게, 끝에선 천천히 멈춘다. 자막은 아래쪽 y=1640 에 반투명 검정 둥근 상자를 깔고 그 위에 흰 글씨로 얹는다.

효과음도 사지 않고 수식으로 만든다

타격음 파일도 없다. ffmpeg 의 aevalsrc 에 식 하나를 넣어 0.3초짜리 소리를 만든다.

코드
        src = "aevalsrc='0.9*sin(2*PI*(90-200*t)*t)*exp(-14*t)+0.25*(random(0)-0.5)*exp(-25*t)':s=44100:d=0.3"

앞쪽은 주파수가 내려가는 저음 사인파가 빠르게 잦아드는 소리, 뒤쪽은 그보다 더 빨리 사라지는 잡음이다. 둘을 더하면 짧은 "펑"이 된다. 대본의 hit 과 bolt 시각마다 이 소리를 adelay 로 밀어 놓고 amix 로 한 트랙에 섞는다.

프레임은 JPG 로 한 장씩 떨군 뒤 ffmpeg 가 30fps 로 묶는다. libx264, crf 20, +faststart 까지 붙이면 바로 올릴 수 있는 mp4 가 나온다.

빛은 검정 위에 더해서 그린다

영상 안에 박아 넣는 이펙트 말고, 편집 앱에 얹어 쓸 스킬 이펙트도 fx.py 로 따로 뽑는다. 닉네임 펑(흰색, 빨강, NPC 세 벌), 번개 강타, 화염 폭발, 검기 베기, 타격 충격파, 레벨업, 기 모으기. 모두 아홉 개다.

여기서 요령이 하나 있다. 빛을 "투명 바탕 위의 색"으로 그리지 않고 "검정 위에 더하는 빛"으로 그린다. 검정 캔버스에 선명한 모양을 그리고, 그걸 반지름 3, 10, 28 로 흐린 판을 가중치 1, 0.8, 0.6 으로 더한다. 번짐이 겹칠수록 밝아지니 진짜 빛처럼 가운데가 하얗게 달아오른다.

화염은 색을 수명에 따라 바꾼다. 갓 튄 불똥은 흰색이고 노랑, 주황, 빨강을 지나 어둡게 꺼진다. 번개 강타는 릴스 안의 번개보다 한 단계 더 나간다. 세 프레임마다 새 시드로 번개 모양을 다시 그려 줄기가 계속 바뀌고, 내려가는 길에 25% 확률로 잔가지가 갈라진다. 땅에 닿으면 납작한 충격 고리가 퍼지고 불티 40개가 튀며, 처음 0.12초는 화면 전체가 한 번 번쩍인다. 레벨업은 발밑에서 빛기둥이 솟고 고리 두 개가 번갈아 퍼지는 동안 「LEVEL UP!」 글자가 튀어나온다.

닉네임 펑은 글자가 0 에서 1.35 배로 튀어나왔다가 출렁이며 1 배에 자리 잡고, 처음 0.25초는 하얗게 달아올랐다가 제 색으로 돌아온다.

투명판과 검정판, 왜 두 벌일까

이펙트마다 파일을 두 개 낸다. 쓰는 곳이 다르기 때문이다.

파일형식쓰는 곳
<이름>-투명.movProRes 4444, 알파 있음캡컷 PC·프리미어·다빈치에 그대로 얹는다
<이름>-검정.mp4검정 배경폰 캡컷에서 「혼합 → 스크린」으로 얹으면 검정이 사라진다

빛을 더해서 그려 둔 덕에 두 벌이 공짜로 나온다. 검정판은 그린 그대로 잘라 담으면 끝이다. 투명판은 밝기를 알파로 바꾼다.

코드
def to_rgba(light, top):
    a = np.clip(light.max(axis=2), 0, 1)
    rgb = np.clip(light / np.maximum(a[..., None], 1e-4), 0, 1)
    img = Image.fromarray((np.dstack([rgb, a]) * 255).astype(np.uint8), "RGBA")
    if top is not None: img.alpha_composite(top)
    return img

세 채널 중 가장 밝은 값을 알파로 쓰고, 색은 그 알파로 나눠 원래 밝기를 되찾는다. 검정이던 곳은 알파가 0 이 되어 사라진다. 스크린 혼합에서 검정이 사라지는 것과 같은 결과를 알파로 미리 만들어 두는 셈이다. 글자처럼 빛이 아닌 것은 따로 RGBA 판에 그려 두었다가 맨 위에 덮는다. 아홉 개를 전부 이은 미리보기 영상도 하나 같이 나온다.

그림 AI 에게는 빈자리까지 정해서 부탁한다

숏폼용 배경은 그림 AI 에게 따로 부탁했다. 요청서에는 그림체부터 자리 배치까지 적었다. 그림체는 앞서 만든 오리툰과 같게, 따뜻한 크림·베이지 파스텔에 굵은 검정 테두리. 세로 1024×1536 한 장에 배경 하나, 캐릭터나 글자나 로고는 넣지 않는다. 가운데부터 아래쪽 2/3 는 비워 둔다. 나중에 캐릭터를 그 자리에 얹기 때문이다. 바닥선은 아래에서 1/4 높이쯤, 위쪽 1/5 은 「Lv.25 오리」 이름표가 올라갈 자리라 하늘이나 어두운 벽처럼 단순하게 둔다.

장소는 여섯 곳이다. 지하감옥 정문, 시작 마을 광장, 성 앞 결투장, 길드 주점, 파란 하늘, 보스방. 캐릭터는 원하면 따로 투명 PNG 로 받는다. 오리와 친구를 각각 평소, 놀람, 화남, 웃음 네 표정씩.

이렇게 역할을 가르면 그림 AI 에게는 "그림"만 남는다. 흔들림, 번쩍, 번개, 소리, 자막, 타이밍은 전부 코드 쪽에 있고, 코드는 몇 번을 다시 돌려도 돈이 들지 않는다. 배경 한 장이 마음에 안 들면 그 한 장만 다시 받으면 되고, 컷 순서를 바꾸고 싶으면 JSON 을 고친다.

핵심 한 줄그림만 맡기고 움직임은 코드로 쥐고 있으면 몇 번을 다시 뽑아도 공짜다.

자주 묻는 것

폰 캡컷에서 이펙트 검정 배경 없애는 법

검정 배경판을 얹고 혼합 모드를 스크린으로 바꾸면 검정이 사라진다. PC 의 캡컷이나 프리미어, 다빈치에서는 알파가 있는 ProRes 4444 투명판을 그대로 얹으면 된다.

브루니가 적는다

색을 바꾸며 숨는 카멜레온이다. AI 와 같이 만들며 걸린 곳을 한 편씩 적는다. 같은 데서 두 번 걸리지 않으려고 쓰는 기록이다.

AI 글 106편 모두 보기 →