• Puppeteer๋Š” DevTools Protocol์„ ํ†ตํ•ด Chrome ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋งคํ‹ฑํ•˜๊ฒŒ ์ œ์–ดํ•˜๋Š” Node.js ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • Headless Chrome์€ Headless ๋ชจ๋“œ๋กœ ์‹คํ–‰๋˜๋Š”, GUI ์—†์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋™์ž‘ํ•˜๋Š” Chrome ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค
  • ์Šคํฌ๋ฆฐ์ƒท ์บก์ฒ˜, PDF ์ƒ์„ฑ, DOM ์กฐ์ž‘, ๋„คํŠธ์›Œํฌ ๊ฐ์‹œ ๋“ฑ์„ ์ž๋™ํ™”ํ•˜๋Š” ๋ธŒ๋ผ์šฐ์ € ์ž๋™ํ™” ๋„๊ตฌ

ํ•ด๋‹น ๊ฐœ๋…์ด ํ•„์š”ํ•œ ์ด์œ 

  • Hyperframes์˜ ๋ Œ๋”๋ง ์—”์ง„์€ Puppeteer๋กœ Headless Chrome์„ ๊ตฌ๋™ํ•˜์—ฌ ๊ฐ ํ”„๋ ˆ์ž„์„ ์บก์ฒ˜ํ•œ๋‹ค
  • HTML Composition์„ Chrome์— ๋กœ๋“œ โ†’ ํ”„๋ ˆ์ž„๋ณ„ seek โ†’ ํ™”๋ฉด ์บก์ฒ˜ โ†’ FFmpeg ์ธ์ฝ”๋”ฉ์ด๋ผ๋Š” ํŒŒ์ดํ”„๋ผ์ธ์˜ ํ•ต์‹ฌ ์ถ•
  • ์ผ๋ฐ˜ ์Šคํฌ๋ฆฐ์ƒท๊ณผ ๋‹ฌ๋ฆฌ, Chrome์˜ BeginFrame API๋ฅผ ํ™œ์šฉํ•ด ๊ฒฐ์ •๋ก ์ (deterministic) ํ”„๋ ˆ์ž„ ์บก์ฒ˜๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค

AS-IS โ€” ์ „ํ†ต์  ๋น„๋””์˜ค ๋ Œ๋”๋ง

sequenceDiagram
    autonumber
    participant ํŽธ์ง‘๊ธฐ as ๋น„๋””์˜ค ํŽธ์ง‘ SW
    participant GPU
    participant ์ถœ๋ ฅ as MP4

    ํŽธ์ง‘๊ธฐ->>GPU: ์‹ค์‹œ๊ฐ„ ๋ Œ๋”๋ง (wall-clock ์˜์กด)
    GPU->>์ถœ๋ ฅ: ํ”„๋ ˆ์ž„ ์ธ์ฝ”๋”ฉ
    Note over ํŽธ์ง‘๊ธฐ,์ถœ๋ ฅ: ์‹คํ–‰ ํ™˜๊ฒฝ(GPU, OS)์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Œ

TO-BE โ€” Hyperframes์˜ Puppeteer ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง

sequenceDiagram
    autonumber
    participant Engine as @hyperframes/engine
    participant Puppeteer
    participant Chrome as Headless Chrome
    participant Page as Composition HTML

    Engine->>Puppeteer: ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค ์š”์ฒญ
    Puppeteer->>Chrome: DevTools Protocol๋กœ Headless Chrome ์‹คํ–‰
    Engine->>Chrome: Composition HTML ๋กœ๋“œ
    Chrome->>Page: window.__hf (seek protocol) ๋Œ€๊ธฐ

    loop ๊ฐ ํ”„๋ ˆ์ž„ (0 ~ totalFrames)
        Engine->>Page: window.__hf.seek(frame / fps)
        Page->>Page: GSAP timeline.totalTime() โ†’ DOM ์—…๋ฐ์ดํŠธ
        Engine->>Chrome: BeginFrame API๋กœ ํ”„๋ ˆ์ž„ ์บก์ฒ˜
        Chrome-->>Engine: PNG ๋ฒ„ํผ ๋ฐ˜ํ™˜
    end

    Engine->>Engine: FFmpeg๋กœ ์ธ์ฝ”๋”ฉ โ†’ MP4

Headless Chrome์ด๋ž€

์ผ๋ฐ˜ Chrome ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™”๋ฉด ํ‘œ์‹œ(GUI) ๋ถ€๋ถ„๋งŒ ์ œ๊ฑฐํ•œ ๊ฒƒ. ๋ Œ๋”๋ง ์—”์ง„(Blink), JavaScript ์—”์ง„(V8), ๋„คํŠธ์›Œํฌ ์Šคํƒ ๋“ฑ์€ ๋ชจ๋‘ ๋™์ผํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค. Chrome 112๋ถ€ํ„ฐ headless ๋ชจ๋“œ๋Š” headful Chrome๊ณผ ๋™์ผํ•œ ์ฝ”๋“œ๋ฒ ์ด์Šค๋ฅผ ๊ณต์œ ํ•œ๋‹ค.

๋‘ ๊ฐ€์ง€ ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ์กด์žฌํ•œ๋‹ค:

๋ฐ”์ด๋„ˆ๋ฆฌ์šฉ๋„
chrome --headless์ผ๋ฐ˜ Chrome์„ headless ๋ชจ๋“œ๋กœ ์‹คํ–‰
chrome-headless-shell๊ฒฝ๋Ÿ‰ ์ „์šฉ ๋ฐ”์ด๋„ˆ๋ฆฌ โ€” Hyperframes Docker ๋ชจ๋“œ์—์„œ ์‚ฌ์šฉ

Puppeteer์˜ ์—ญํ• 

Puppeteer๋Š” Chrome๊ณผ ์‚ฌ๋žŒ ์‚ฌ์ด์˜ ๋ฆฌ๋ชจ์ปจ ์—ญํ• ์„ ํ•œ๋‹ค:

Node.js ์ฝ”๋“œ โ†’ Puppeteer API โ†’ DevTools Protocol (WebSocket) โ†’ Chrome

์ฃผ์š” ๊ธฐ๋Šฅ:

๊ธฐ๋Šฅ์„ค๋ช…
ํŽ˜์ด์ง€ ํƒ์ƒ‰URL ๋กœ๋“œ, ์ƒˆ ํƒญ ์—ด๊ธฐ
DOM ์กฐ์ž‘page.evaluate()๋กœ ํŽ˜์ด์ง€ ๋‚ด JS ์‹คํ–‰
์Šคํฌ๋ฆฐ์ƒทpage.screenshot()์œผ๋กœ ํ˜„์žฌ ํ™”๋ฉด PNG ์บก์ฒ˜
CDP ์„ธ์…˜Chrome DevTools Protocol์— ์ง์ ‘ ๋ช…๋ น ์ „์†ก

BeginFrame API โ€” ๊ฒฐ์ •๋ก ์  ์บก์ฒ˜์˜ ํ•ต์‹ฌ

์ผ๋ฐ˜ ์Šคํฌ๋ฆฐ์ƒท(Page.captureScreenshot)์€ Chrome์ด ์ž์ฒด ํƒ€์ด๋ฐ์œผ๋กœ ํ™”๋ฉด์„ ๊ฐฑ์‹ ํ•œ โ€œ์Šค๋ƒ…์ƒทโ€์„ ์ฐ๋Š” ๊ฒƒ์ด๋‹ค. ๋ฐ˜๋ฉด BeginFrame API๋Š” ์—”์ง„์ด Chrome์˜ ๋ Œ๋”๋ง ์‚ฌ์ดํด์„ ์ง์ ‘ ์ œ์–ดํ•œ๋‹ค:

๋ฐฉ์‹ํƒ€์ด๋ฐ ์ œ์–ด๊ฒฐ์ •๋ก ์„ฑ
Page.captureScreenshotChrome ์ž์ฒด (wall-clock)๋น„๊ฒฐ์ •๋ก ์ 
HeadlessExperimental.beginFrame์—”์ง„์ด ์ง์ ‘ ์ œ์–ด๊ฒฐ์ •๋ก ์ 

Hyperframes ์—”์ง„์˜ ์บก์ฒ˜ ํ๋ฆ„:

  1. seek(time) ํ˜ธ์ถœ โ†’ DOM/์• ๋‹ˆ๋ฉ”์ด์…˜ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
  2. beginFrame(frameTimeTicks, interval) ํ˜ธ์ถœ โ†’ Chrome์ด ์ •ํ™•ํžˆ ์ด ์‹œ์ ์˜ ํ”„๋ ˆ์ž„์„ ํ•ฉ์„ฑ(composite)
  3. ํ•ฉ์„ฑ๋œ ํ”„๋ ˆ์ž„ ๋ฐ์ดํ„ฐ๋ฅผ PNG ๋ฒ„ํผ๋กœ ๋ฐ˜ํ™˜

์ด ๋ฐฉ์‹ ๋•๋ถ„์— CPU ์†๋„, OS, ์‹œ์Šคํ…œ ๋ถ€ํ•˜์— ๊ด€๊ณ„์—†์ด ๋™์ผํ•œ ์ž…๋ ฅ โ†’ ๋™์ผํ•œ ํ”ฝ์…€ ์ถœ๋ ฅ์ด ๋ณด์žฅ๋œ๋‹ค.

window.__hf โ€” Seek Protocol

Hyperframes ์—”์ง„๊ณผ Composition HTML ์‚ฌ์ด์˜ ์œ ์ผํ•œ ๊ณ„์•ฝ:

interface HfProtocol {
  duration: number;                    // ์ „์ฒด ๊ธธ์ด(์ดˆ)
  seek(time: number): void;            // ํ•ด๋‹น ์‹œ์ ์œผ๋กœ ์ด๋™
  media?: HfMediaElement[];            // ๋ฏธ๋””์–ด ์š”์†Œ ์„ ์–ธ
  transitions?: HfTransitionMeta[];    // ํŠธ๋žœ์ง€์…˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
}
 
// ํŽ˜์ด์ง€์—์„œ ์ด ๊ฐ์ฒด๋ฅผ ๋…ธ์ถœํ•˜๋ฉด ์—”์ง„์ด ์บก์ฒ˜๋ฅผ ์‹œ์ž‘ํ•œ๋‹ค
window.__hf = { duration, seek, media };

์—”์ง„์€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ „ํ˜€ ๋ชจ๋ฅธ๋‹ค. GSAP์ด๋“  CSS๋“  Three.js๋“ , seek()์ด ๊ฒฐ์ •๋ก ์  ์ถœ๋ ฅ์„ ๋ณด์žฅํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

Hyperframes์—์„œ์˜ ๋ธŒ๋ผ์šฐ์ € ๊ด€๋ฆฌ

์—”์ง„์€ Puppeteer ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค๋ฅผ **ํ’€๋ง(pooling)**ํ•˜์—ฌ ์žฌ์‚ฌ์šฉํ•œ๋‹ค:

  • acquireBrowser() โ€” ํ’€์—์„œ ๋ธŒ๋ผ์šฐ์ € ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ด (์—†์œผ๋ฉด ์ƒˆ๋กœ ์‹คํ–‰)
  • releaseBrowser() โ€” ์‚ฌ์šฉ ํ›„ ํ’€์— ๋ฐ˜ํ™˜
  • ์บก์ฒ˜ ๋ชจ๋“œ๋ฅผ ์ž๋™ ๊ฐ์ง€: Linux์—์„œ chrome-headless-shell์ด ์žˆ์œผ๋ฉด beginframe, ์•„๋‹ˆ๋ฉด screenshot ํด๋ฐฑ

์ฐธ๊ณ  ๋ฌธ์„œ