모션 프로모션 비디오 (HTML → MP4)
결과물: 준비된 MP4 (H.264, yuv420p, AAC)를 SendUserFile로 전송합니다. 응답은 pt-BR로 짧게 작성하세요.
1. 최소 브리핑 (너무 많이 묻지 마세요)
기본값을 설정하고 결과를 바꾸는 것만 물어보세요:
- 형식: 9:16 (1080×1920) 기본값; 16:9 (1920×1080) 요청 시. 지속 시간: Insta 광고 20–35초; 페이싱 느림 (각 단계 ~1.5–4초) — 사용자가 이미 "2배속 같다"고 불평했습니다.
- 대상: 젊은 층/SaaS → 밝은 SFX (종소리, 팝, 클릭). 나이 든/프리미엄 대상 (시계, 보석) → 차분한: 패드, 부드러운 임팩트, 우쉬, 샤이머; 어린이용 팝 금지.
- 브랜드: 팔레트, 폰트, 로고, CTA (WhatsApp/URL), 스크린샷/사진. 음악: 사용자가 트랙(Spotify 등)을 언급하면 오디오에 접근하지 말고 알린 후, 오리지널 트랙을 사용하고 사용자가 mp3를 보내면 동기화하겠다고 제안하세요 (라이선스 확인).
2. 에셋 준비
- 제품 사진:
rembg(isnet-general-use 모델)로 배경 제거 → 알파가 있는 PNG로 "3D" 효과 (떠다님, rotateY/rotateX + perspective, PNG 알파로 마스킹된sheen광택 via-webkit-mask-image). - 저해상도 스크린샷: Lanczos 3배 업스케일 + UnsharpMask. 사용자가 "광고 없이" 요청하면 원본 사진에서 광고 텍스트 제거.
- 폰트: Inter / Inter Display ExtraBold (SaaS, 플랫 스타일), Lora serif + 골드 이탤릭 (프리미엄), Poppins.
3. 사용자가 승인한 스타일
- 단색 블록, 큰 카드, 마스킹된 단어별 리빌이 있는 거대한 타이포그래피, 지속적인 움직임 (느린 패럴랙스, 느린 줌 1→1.15, 별/입자), 비트에 동기화된 3개의 컬러 패널 와이프 컷.
- 카메라 흔들림 없음 (사용자가 shake 거부): 최대 shake ×0.12, 카메라 회전 없음, drift ≤3 px.
- 내러티브 구조: 갈고리/문제 → 해결책 → 2–3 기능/통합 → 증거/혜택 → CTA. 각 씬 3–5초, 씬 사이에 와이프. SaaS에서는 해결책 전에 구체적인 문제(답장 없는 메시지, 손실된 매출 -R$)를 보여주세요.
- 짧은 텍스트, 줄당 2–4단어, pt-BR, 한 단어 강조 (색/이탤릭). Instagram safe zone 준수 (9:16에서 상단 약 250px, 하단 약 400px 피해서 CTA/캡션).
4. 엔진 (결정론적 HTML)
video.html 파일 하나에 window.seek(T) 함수가 있어 시간 T에 따라 모든 것을 배치합니다 (requestAnimationFrame/CSS 애니메이션 없음). 핵심:
const cl=(x,a=0,b=1)=>Math.min(b,Math.max(a,x)),lerp=(a,b,p)=>a+(b-a)*p;
const E={outBack:(p,c=1.8)=>1+(c+1)*Math.pow(p-1,3)+c*Math.pow(p-1,2),outExpo:p=>p>=1?1:1-Math.pow(2,-10*p),outQuart:p=>1-Math.pow(1-p,4),inOutQuart:p=>p<.5?8*p**4:1-Math.pow(-2*p+2,4)/2,inOutCubic:p=>p<.5?4*p**3:1-Math.pow(-2*p+2,3)/2,lin:p=>p,inCubic:p=>p**3,inOutSine:p=>-(Math.cos(Math.PI*p)-1)/2};
const ez=(T,a,d,f=E.outExpo)=>f(cl((T-a)/d));
function mk(parent,html,cls='',style=''){const d=document.createElement('div');d.className='abs '+cls;d.style.cssText=style;d.innerHTML=html;parent.appendChild(d);return d} // .abs{position:absolute;left:50%;top:50%}
function P(el,o){const x=o.x||0,y=o.y||0,s=o.s==null?1:o.s,op=o.o==null?1:o.o,r=o.r||0,rx=o.rx||0,ry=o.ry||0;el.style.transform=`perspective(2400px) translate(-50%,-50%) translate(${x}px,${y}px) rotateX(${rx}deg) rotateY(${ry}deg) rotate(${r}deg) scale(${s})`;el.style.opacity=op;el.style.visibility=op<=.003?'hidden':'visible'}
function words(el,arr){el.innerHTML=arr.map(w=>`<span class="mk"><span class="wd ${w[1]||''}">${w[0]}</span></span>`).join(' ')} // .mk{display:inline-block;overflow:hidden;padding:.14em .05em .24em;margin:-.14em -.05em -.24em}
function lineIO(el,T,tin,tout,step=.1,d=1.0){[...el.querySelectorAll('.wd')].forEach((w,i)=>{const a=ez(T,tin+i*step,d,E.outQuart),b=tout?ez(T,tout+i*step*.5,.5,E.inCubic):0;w.style.transform=`translateY(${(1-a)*118-b*118}%)`;w.style.opacity=cl(a*2.2,0,1)*(1-b)})}
function addScene(a,b,bg,build,upd,cam){/* .sc div 생성, bg, inner; scenes.push({a,b,root,inner,obj:build(inner),upd,cam}) */}
function runScenes(T){/* T가 [a-.02,b+.02] 범위에 있을 때만 표시; inner.style.transform = cam(t,T)의 translate/scale; s.upd(t,obj,T) */}
// 와이프: 3개 패널 [어두운 색, 골드/액센트, 다음 씬 배경색], 각각 ez(T,t0+i*.09,.38,E.inOutQuart)로 들어오고 나감 (t0=wipe-.55); 방향 x / xr 교대
function seek(T){runScenes(T);drawWipes(T);drawFX(T);drawGrain(T);/* 마지막 페이드 블랙 */}
세계 위의 고정 레이어: 먼지/입자 캔버스, 와이프, 필름 그레인 (알파 .07), 비네트, fade-in/out용 검은 베일.
제품 3D 효과: P(hero,{ry:Math.sin(T*.7)*8, rx:Math.sin(T*.5)*3, y:Math.sin(T*1.1)*14, s}) + sweep() (PNG로 마스킹된 mix-blend-mode:soft-light 광택 스트립) + 뒤에 방사형 골드 글로우 + drop-shadow 그림자.
시계형 오프닝 (v1 시계에서 사용자 승인): SVG 760px, 60개 틱 (5개마다 더 굵고 골드), 그려지는 골드 아크 (stroke-dashoffset), 0.5초마다 6°씩 전진하는 바늘 (E.outBack), 지나간 틱은 켜짐; 텍스트 "O tempo passa pra todo mundo. / Poucos deixam marca."; 회전하는 별표 ✦. 제품이 허용할 때 이 유형의 테마 오프닝을 재사용하세요.
가변 속도 타임라인: SEG=[[ini,fim,fator],…]를 seek와 오디오의 M(t) 함수에 적용하여 SFX가 컷을 따르도록 함.
5. 렌더링 (결정론적, 재개 가능)
- Playwright Chromium 뷰포트 1080×1920 (또는 1920×1080),
page.evaluate('seek(t)')+screenshot(type='jpeg',quality=92). - 60fps + 모션 블러: 프레임당 2 서브프레임 (t=(f+(s+.5)/2-.5)/60), numpy float32 평균 → JPEG q95 →
ffmpeg -f image2pipe -c:v mjpeg -i - -c:v libx264 -preset fast -crf 18 -pix_fmt yuv420p seg/NNNNN.mp4. seg/에 120프레임 청크, 2개 병렬 프로세스 (짝수/홀수 청크), 기존 청크 건너뜀 (컨테이너 재시작 가능).setsid nohup ./go.sh &로 실행하고, until-loop로 파일 완료를 모니터링하는 Monitor 사용 (foreground sleep은 차단됨; 30분 후 만료 시 Monitor 재활성화). ~0.38초/스크린샷, 2 CPU: 24초 ≈ 1440프레임 ≈ 25–30분.- 시작 부분만 변경? 영향을 받는 청크만 삭제하고 재렌더링; 오디오만 변경되면
-c:v copy로 리먹스. - 연결:
ffmpeg -f concat -safe 0 -i list.txt -i audio.wav -c:v libx264 -crf 21 -pix_fmt yuv420p -c:a aac -b:a 192k -shortest -movflags +faststart out.mp4. - 전체 렌더링 전에 핵심 시간대에
seek()스크린샷 2–4장 찍어 두 형식 모두에서 텍스트 잘림/오버플로우 (이전에 큰 폰트에서 발생) 및 JS 오류 (page.on('pageerror')) 확인.
6. 오디오 (numpy/scipy, 합성)
헬퍼: lp/hp/bp (butter), add(buf,t,x,g), tt(d); 사운드: thump (사인 46–96 Hz 감쇠), impact (thump + lp 700 노이즈), riser (상승 대역 노이즈), swish (bp 1.8–5.5 kHz 노이즈, sin² 윈도우), shimmer (감쇠 비조화 부분), tick (사인 ~900 Hz, decay 90), pad (3개 디튠 사인 lp 1.4 kHz + 페이드), pluck; 리버브는 감쇠 노이즈와의 컨볼루션.
- SaaS/젊은 층: 밝은 종소리, 팝, 클릭, 우쉬 — 무거운 저음 사용 금지 (사용자가 "모든 게 저음"이라고 싫어함).
- 프리미엄/나이 든 층: 와이프에 riser+impact, 텍스트 리빌에 swish, 제품 등장에 shimmer, 목록에 틱, 시계 오프닝에 0.5초마다 시계 틱, 느린 코드 진행 패드 (Dm–Bb–F–C), 부드러운 비트 ~90 bpm. 비트 80–90 bpm은 4초 컷과 잘 어울림.
- 마스터링 ("터짐" 방지): 핫 믹스에
tanh절대 금지.x=x/np.abs(x).max()*.85적용, wav 저장 후ffmpeg -af loudnorm=I=-16:TP=-2:LRA=11 -ar 44100실행. 피크 < -1 dBFS, 크레스트 팩터 > 10 dB 확인.
7. 전달 및 반복
MP4를 한 줄 캡션과 함께 전송하고, 변경 사항과 확인되지 않은 사항을 언급하세요 (예: "프레임과 메트릭 확인했지만 오디오는 듣지 못했습니다"). 일반적인 피드백 및 응답: "너무 빠름" → 타임라인 ×1.5 스케일; "떨림" → shake 감소; "소리 나쁨" → SFX 프로필 변경 (밝음 vs. 차분함); "터짐" → 오디오만 리마스터 후 리먹스; "v1 애니메이션 X가 좋았음" → 씬 X 다시 가져오고 영향받은 청크만 재렌더링. 사용자가 재사용 가능한 프롬프트/문서를 요청하면 이 흐름으로 문서를 생성하세요.