2 Models Available
한 감독이 5초짜리 AI 비디오 클립에서 가장 강렬한 프레임을 뽑아내, 시퀀스의 다음 샷을 위한 레퍼런스 이미지로 사용합니다. Astorie 캔버스에서 소스 클립(Seedance 2, Kling 3 등)을 프레임 추출 도구 노드로 보내고, 원하는 타임스탬프까지 스크러빙한 뒤, 추출한 스틸 이미지를 Nano Banana 2 이미지 편집으로 이어 붙이거나 다음 비디오 노드에 시작 프레임으로 바로 연결하세요. 결과물은 다음 샷의 시작 프레임, 이미지 편집 체인, 또는 승인된 비디오 테이크에서 뽑은 히어로 스틸로 쓸 수 있는 레퍼런스 고정 프레임입니다. 아래에서 모델을 선택해 프레임 추출 워크플로우를 따라가 보세요.

스크롤에 동기화된 캔버스 애니메이션을 위해 AI 비디오에서 프레임을 추출하려면 5단계 파이프라인을 거칩니다: 반복 가능한 클립을 생성하고, FFmpeg로 클립을 번호가 매겨진 프레임 시퀀스로 내보낸 뒤, 그 프레임들을 선별해 WebP로 변환하고, drawImage()로 현재 프레임을 HTML <canvas>에 그린 다음, GSAP ScrollTrigger로 스크롤 위치에서 프레임 인덱스를 구동합니다. 결과는 애플 스타일의 "스크러브" 효과입니다. 스크롤할 때 뚝뚝 끊기는 <video> 요소 대신 캔버스에서 클립이 프레임 단위로 재생됩니다.
경쟁 튜토리얼(Codrops, Builder.io, Tympanus)은 모두 이미 가지고 있는 비디오 파일에서 시작해 FFmpeg와 GSAP 절반만 다룹니다. Astorie는 업스트림 절반을 하나의 노드 기반 캔버스로 압축합니다: 코드를 한 줄도 쓰기 전에, 시작-끝 프레임 제어로 완벽하게 반복되는 AI 클립을 생성하고, 프레임을 추출·선별하며, 필요하면 주요 프레임을 4K로 업스케일할 수 있습니다. GSAP에 넘겨줄 결과물은 얼룩진 화면 녹화가 아니라 깔끔하게 중복 제거된 WebP 시퀀스입니다.
파이프라인은 왼쪽에서 오른쪽으로 네 단계입니다: (1) Astorie 캔버스에서 비디오 모델로 반복 구간을 생성한다, (2) 프레임 시퀀스를 추출한다(캔버스 내 프레임 추출 또는 아래의 FFmpeg 대안), (3) 주요 프레임을 선별하고 필요하면 업스케일한다, (4) GSAP ScrollTrigger로 시퀀스를 <canvas>에 렌더링한다. 1~3단계는 캔버스에서 이루어지는 창작 작업이고, 4단계는 여러분이 배포할 코드입니다. 아래 섹션들은 각 단계를 그대로 복사해 쓸 수 있는 명령어와 함께 다룹니다.

클립이 반복될 때 점프컷이 생기면 스크롤 스크러브가 어색해 보이므로, 소스 비디오는 깔끔하게 반복되어야 합니다. Astorie 캔버스에서 비디오 노드를 놓고 첫-끝 프레임 제어를 사용하세요: 시작 프레임과 끝 프레임에 같은 이미지를 설정하면, 모델이 시작 지점으로 되돌아오는 움직임 경로를 보간합니다. 첫-끝 프레임 조건화를 지원하는 모델로는 Kling, Luma, Runway, Seedance가 있습니다. 매끄러운 루프가 필요하지 않은 클립이라면 Veo, Sora, Wan 같은 텍스트-투-비디오 모델이 더 낫습니다.
프롬프트와 시작/끝 프레임을 여러 비디오 노드에 한 번에 연결해 분산 실행하세요: 하나의 소스 이미지를 Kling 3, Seedance 2, Runway Gen-4에 동시에 넣고, 모든 결과물을 버전 트레이에 남긴 뒤 가장 깔끔하게 반복되는 것을 고르세요. 이 부분이 바로 경쟁사가 단순한 비디오 파일만으로는 할 수 없는 지점입니다 — 여러분은 이미 촬영한 것을 어떻게든 살리는 것이 아니라, 추출 전에 가장 좋은 원재료를 고르고 있는 것입니다.
클립은 짧게, 움직임은 끊기지 않게 유지하세요. 24~30fps로 3~5초짜리 루프를 만들면 대략 72~150개의 소스 프레임이 나오는데, 이는 스크롤 시퀀스에 적합한 규모입니다(3단계에서 이를 솎아낼 것입니다). 클립 안에서 강한 컷, 빠른 팬, 심한 모션 블러는 피하세요. 이런 요소들은 움직일 때는 괜찮아 보이지만 사용자가 스크롤 도중 한 프레임에 멈춰 서면 지저분한 프레임을 만들어냅니다.
클립을 스틸 이미지로 만드는 방법은 두 가지입니다. Astorie 캔버스에서 승인된 비디오를 프레임 추출 도구 노드로 보내고 스크러빙해서 개별 주요 프레임을 뽑아내는 방법은, 이미지 편집 체인이나 다음 샷의 시작 프레임을 위해 깨끗한 스틸 몇 장만 필요할 때 빠릅니다. 스크롤 스크러브용으로 균등한 간격의 전체 시퀀스가 필요하다면, 클립을 내보낸 뒤 개발자 SERP가 기대하는 사실상의 표준인 FFmpeg를 실행하세요.
아래 명령어는 소스 프레임마다 번호가 매겨진 PNG를 씁니다. -vf fps=30을 사용하면 클립의 원래 프레임레이트와 상관없이 고정된 초당 30프레임으로 리샘플링되며, 인덱스에 0을 채워 파일이 올바르게 정렬되도록 합니다. 먼저 PNG로 추출해야 무손실이 유지됩니다. WebP 변환은 다음 명령어에서 진행합니다.
PNG 시퀀스를 WebP로 변환하면 스크롤 스크러브 크기에서 눈에 띄는 화질 손실 없이 용량을 60~80% 줄일 수 있습니다. WebP는 실제로 브라우저에 전달하는 포맷입니다 — 150프레임짜리 PNG 시퀀스는 40MB를 넘을 수 있지만, 같은 시퀀스를 품질 80의 WebP로 만들면 대개 8MB 미만입니다. cwebp로 폴더 전체를 순회하거나, FFmpeg가 WebP를 직접 출력하도록 하세요.
완성된 시퀀스를 Next.js 프로젝트의 public/frames/ 아래에 넣어, 안정적이고 캐시하기 좋은 경로(예: /frames/frame_0001.webp)에서 파일이 제공되도록 하세요. 번호가 매겨지고 0으로 채워진 파일명 덕분에 4단계에서 단순한 문자열 삽입만으로 어떤 인덱스의 URL이든 만들 수 있습니다.

FFmpeg — extract a numbered frame sequence at 30fps
# Resample to a fixed 30fps and write zero-padded PNG frames
ffmpeg -i input.mp4 -vf "fps=30" frames/frame_%04d.png
# Variant: keep the clip's native frame rate (one PNG per source frame)
ffmpeg -i input.mp4 frames/frame_%04d.pngWebP conversion — shrink the payload 60-80%
# Convert every PNG in the folder to quality-80 WebP
for f in frames/*.png; do
cwebp -q 80 "$f" -o "${f%.png}.webp"
done
# Or have FFmpeg emit WebP directly (skip the PNG step)
ffmpeg -i input.mp4 -vf "fps=30" -quality 80 public/frames/frame_%04d.webpAI 비디오에는 품질이 떨어지는 프레임이 섞여 있습니다: 모션 블러가 낀 중간 프레임, 가끔 발생하는 왜곡, 이따금 나타나는 아티팩트까지. 시퀀스를 배포하기 전에 Astorie 캔버스에 프레임들을 펼쳐놓고 선별하세요 — 흐릿한 프레임은 삭제하고 선명한 프레임만 남기며, 루프의 첫 프레임과 끝 프레임이 일치하는지 확인해 스크러브가 깔끔하게 이어지도록 하세요. 150프레임짜리 내보내기를 깔끔한 90프레임으로 솎아내면 브라우저가 미리 불러와야 할 용량도 줄어듭니다.
타이틀 장면이나 제품 클로즈업처럼 어떤 주요 프레임이 화면 전체 너비로 고정될 예정이라면, 레티나 디스플레이에서도 선명하게 보이도록 4K로 업스케일하세요. 선택한 프레임을 업스케일 도구 노드로 보낸 뒤 결과물을 다시 시퀀스에 넣으세요. 해상도 계산과 사례별 가이드는 「이미지를 4K로 업스케일하는 방법」을, 프레임 하나가 아니라 클립 전체를 업스케일하려면 「비디오를 4K로 업스케일하는 방법」을 참고하세요.
레퍼런스급으로 깨끗한 프레임은 스크롤 스크러브 이외의 용도로도 재사용할 수 있습니다: 다시 Nano Banana 2 이미지 편집 체인에 넣거나, 다음 비디오 노드의 시작 프레임으로 사용해 다음 샷이 정확히 같은 룩을 물려받도록 할 수 있습니다. 이는 클립을 연장할 때 쓰는 것과 같은 프레임 추출 기법입니다 — 마지막으로 좋은 프레임을 추출해 다음 이미지-투-비디오 생성의 시작 프레임 입력으로 넣으면 됩니다(「비디오 클립 연장하는 방법」 참고).

이제 코드 절반입니다. WebP 시퀀스를 Image 객체 배열로 미리 불러오고, drawImage()로 현재 프레임을 <canvas>에 그린 다음, GSAP ScrollTrigger가 스크롤 진행도를 프레임 인덱스에 매핑하도록 하세요. 사용자가 고정된 긴 구간을 스크롤하면 인덱스가 0에서 마지막 프레임까지 올라가며 캔버스가 다시 그려집니다 — 이것이 스크러브의 전부입니다.
선명한 스크러브와 흐릿하고 잔상이 남는 스크러브를 가르는 세부 사항이 두 가지 있습니다. 첫째, window.devicePixelRatio만큼 캔버스를 확대해 레티나 화면에서 네이티브 해상도로 렌더링한 뒤 CSS로 크기를 줄이세요 — 고해상도(high-DPI) 화면에서 논리 픽셀 그대로 그리면 흐릿한 이미지가 나옵니다. 둘째, 매번 drawImage() 전에 ctx.clearRect()를 호출하세요(또는 캔버스 전체를 덮는 불투명한 프레임을 그리세요). 그렇지 않으면 반투명 프레임이 쌓여 잔상이 남습니다.
아래 코드는 핵심이 되는 패턴입니다: render(index) 함수, setupCanvas() 헬퍼 안의 devicePixelRatio 스케일링, 매 그리기 전의 clearRect, 그리고 onUpdate에서 self.progress를 가장 가까운 프레임으로 스냅하는 ScrollTrigger. frameCount와 /frames/ 경로는 2단계에서 내보낸 결과에 맞게 조정하세요.
GSAP ScrollTrigger + canvas image-sequence scrub
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
const canvas = document.querySelector("#sequence");
const ctx = canvas.getContext("2d");
const frameCount = 90;
const frames = [];
// High-DPI scaling: draw at native resolution, size down with CSS
function setupCanvas(w, h) {
const dpr = window.devicePixelRatio || 1;
canvas.width = w * dpr;
canvas.height = h * dpr;
canvas.style.width = w + "px";
canvas.style.height = h + "px";
ctx.scale(dpr, dpr);
}
setupCanvas(1280, 720);
// Preload the zero-padded WebP sequence from /public/frames
for (let i = 0; i < frameCount; i++) {
const img = new Image();
img.src = `/frames/frame_${String(i + 1).padStart(4, "0")}.webp`;
frames.push(img);
}
const state = { frame: 0 };
function render(index) {
ctx.clearRect(0, 0, canvas.width, canvas.height); // kill ghosting
ctx.drawImage(frames[index], 0, 0, 1280, 720);
}
frames[0].onload = () => render(0);
// Map scroll progress to a frame index
gsap.to(state, {
frame: frameCount - 1,
snap: "frame",
ease: "none",
scrollTrigger: {
trigger: "#sequence-section",
start: "top top",
end: "+=3000",
scrub: 0.5,
pin: true,
onUpdate: (self) => {
const i = Math.round(self.progress * (frameCount - 1));
render(i);
},
},
});스크롤 스크러브는 본질적으로 프리로딩 문제입니다. 브라우저는 끊김 없이 다시 그릴 수 있을 만큼 충분한 프레임을 가져와야 하므로, 프레임 수를 기기에 맞게 조정하세요: 데스크톱은 대략 90~150프레임, 대역폭과 메모리가 더 빠듯한 모바일은 30~60프레임으로 솎아내세요. 모바일에 데스크톱용 시퀀스를 강제로 내려받게 하는 대신, 더 작은 뷰포트에는 별도의 더 작은 WebP 세트를 제공하세요.
방향성을 가지고 단계적으로 프리로드하세요. 처음 10~20프레임은 즉시 불러와 구간이 바로 상호작용 가능하도록 하고, 나머지는 백그라운드에서 스트리밍하세요. 사용자가 아래로 스크롤 중이라는 것을 안다면, 균일하게 불러오는 대신 현재 인덱스보다 앞선 프레임을 미리 불러오세요. 지원되는 환경에서는 createImageBitmap()으로 메인 스레드 밖에서 프레임을 디코딩해 drawImage()가 스크롤을 절대 막지 않도록 하세요.
용량 문제의 대부분은 WebP가 해결해줍니다. 품질 75~85에서는 프레임당 용량이 충분히 작아서, 150프레임짜리 데스크톱 시퀀스조차 한 자릿수 메가바이트 안에 들어옵니다. 이것이 바로 긴 공백 상태 없이 즉시 프리로드가 가능한 이유입니다. 시퀀스가 여전히 무겁게 느껴진다면 품질을 낮추기 전에 프레임 수부터 줄이세요 — 인간의 눈은 프레임 수가 줄어드는 것은 훨씬 잘 견디지만, 스크롤 중간에 멈춘 흐릿하고 저품질인 프레임은 잘 견디지 못합니다.
GSAP을 <video>에 연결해 스크롤로 currentTime을 설정할 수도 있고, 개발 환경에서는 문제없어 보일 것입니다 — 그러다 실제 기기에서는 버벅거립니다. 브라우저는 비디오를 프레임 단위로 정확하게 탐색하지 않습니다: currentTime을 설정하면 가장 가까운 키프레임으로 스냅되므로, 압축률이 높은 클립을 스크러빙하면 튀고 끊깁니다. 모바일 브라우저는 자동재생 정책에 따라 프로그램적인 재생을 제한하거나 차단하기도 하며, 스크롤할 때마다 키프레임을 디코딩하는 것도 비용이 큽니다.
캔버스 이미지 시퀀스는 이 모든 문제를 피해 갑니다. 각 프레임이 완전히 디코딩된 독립적인 이미지이므로, render(index)는 구조적으로 프레임 단위까지 정확합니다 — 47%로 스크롤하면 매번 정확히 그 프레임을 얻으며, 키프레임 스냅도 없습니다. 아무것도 "재생"되고 있지 않으므로 싸워야 할 자동재생 정책도 없습니다. 그저 스틸 이미지를 그리고 있을 뿐입니다. 대가는 사전 로딩 용량(프레임을 미리 불러와야 함)이며, 이것이 바로 WebP와 기기별 프레임 수 조정이 필요한 이유입니다.
이것이 스크롤 스크러브 애니메이션에 대한 개발자 SERP가 한결같이 캔버스 방식을 지지하는 이유입니다. Astorie가 기여하는 부분은 업스트림입니다: 생성-추출-선별 단계를 모두 담당하므로, 여러분이 이 캔버스 패턴에 넘기는 프레임은 기존 파일에서 화면 녹화로 얻을 수 있는 것과 달리 깨끗하고, 반복 가능하며, 필요한 곳에서는 4K입니다.
ByteDance
Seedance 2.0은 깨끗한 1080p 출력 덕분에 프레임 추출이 유용하게 작동하는 상류 비디오 모델입니다 — 프레임의 구도가 잘 잡혀 있고 안정적이며, 이미지 편집 입력이나 정적 결과물로 쓸 만큼 디테일도 충분합니다. 파이프라인은 다음과 같습니다: 승인된 Seedance 클립을 생성하거나 가져오기 → 프레임 추출 툴 노드 거치기 → 다음 단계를 위해 가장 강력한 프레임을 고르기입니다. 흔한 후속 활용: 추출한 프레임을 Nano Banana 2나 Flux Kontext 이미지 편집의 레퍼런스 이미지로 공급하거나, Sora 2나 Kling 3.0에서 다른 카메라 움직임의 시작 프레임으로 사용하거나, 정적 히어로 이미지로 내보내는 것입니다. 함께 보는 `tools/frame-extraction` 페이지에서 툴 라우팅과 파라미터를 다루며, 이 가이드는 Seedance와 짝을 이루는 파이프라인에 초점을 맞춥니다.
Kling
Kling 3.0은 추출하려는 프레임에 사람이나 인간형 캐릭터가 등장할 때 선택할 상류 모델입니다 — 해부학적으로 정확한 몸, 얼굴, 의상 움직임 덕분에 동급 설정의 다른 비디오 모델보다 훨씬 더 잘 버티는 프레임별 정지 이미지를 만들어 내며, 이미지 편집 입력이나 정적 결과물로 쓰기에 좋습니다. 파이프라인은 Seedance와 동일합니다 — 승인된 Kling 클립을 생성하거나 가져오기 → 프레임 추출 툴 노드 거치기 → 다음 단계를 위해 가장 강력한 프레임 고르기입니다. Kling 소스 추출물의 흔한 후속 활용: AI 캐릭터 시트(클립 중간의 프레임 하나를 추출해 Nano Banana 2에 공급해 의상 변형 만들기), 같은 캐릭터가 등장하는 새 카메라 움직임 샷의 시작 프레임, 또는 인물의 존재감이 핵심인 브랜드 마케팅용 히어로 정지 이미지입니다. 함께 보는 `tools/frame-extraction` 페이지에서 툴 라우팅을 다루며, 이 가이드는 Kling과 짝을 이루는 파이프라인에 초점을 맞춥니다.
FFmpeg로 클립을 번호가 매겨진 이미지 시퀀스로 내보내세요 — ffmpeg -i input.mp4 -vf "fps=30" frames/frame_%04d.png — 그런 다음 웹용으로 WebP로 변환하세요. Astorie 캔버스에서는 비디오를 프레임 추출 도구 노드로 보내고 스크러빙해서 캔버스를 벗어나지 않고도 개별 주요 프레임을 뽑아낼 수도 있습니다.
ffmpeg -i input.mp4 -vf "fps=30" frames/frame_%04d.png를 사용하세요. -vf "fps=30" 필터는 클립의 원래 프레임레이트와 상관없이 고정된 초당 30프레임으로 리샘플링하며, frame_%04d.png는 인덱스에 0을 채워 파일이 시퀀스로 올바르게 정렬되도록 합니다.
캔버스 이미지 시퀀스는 프레임 단위로 정확하고 끊김이 없지만, 스크러빙되는 <video>는 그렇지 않습니다. 비디오 엘리먼트의 currentTime을 설정하면 가장 가까운 키프레임으로 스냅되므로 압축된 클립을 스크러빙하면 끊기고, 모바일 자동재생 정책이 프로그램적인 재생을 아예 막을 수도 있습니다. 미리 디코딩된 독립적인 프레임을 <canvas>에 그리면 사전 로딩이라는 대가를 치르는 대신 정확하고 매끄러운 제어를 얻을 수 있습니다.
데스크톱 스크롤 스크러브에는 대략 90~150프레임, 모바일에는 솎아낸 30~60프레임이 필요합니다. 프레임이 많을수록 움직임은 매끄러워지지만 프리로딩 부담도 커지므로, 작은 뷰포트에는 더 작은 WebP 세트를 제공하고, 용량이 무겁게 느껴진다면 프레임당 품질을 낮추기 전에 프레임 수부터 줄이세요.
프레임들을 배열로 미리 불러온 뒤, onUpdate에서 self.progress를 프레임 인덱스로 변환하는 ScrollTrigger를 만드세요 — const i = Math.round(self.progress * (frameCount - 1)) — 그리고 캔버스를 지우고 frames[i]를 그리는 render(i)를 호출하세요. 구간을 고정(pin)하고 scrub을 설정하면 스크롤 위치가 재생 위치에 바로 매핑됩니다.
window.devicePixelRatio만큼 캔버스를 확대하세요. canvas.width와 canvas.height에 기기 픽셀 비율을 곱하고, CSS의 width/height는 논리적 크기로 설정한 뒤 ctx.scale(dpr, dpr)을 호출하세요 — 이렇게 하면 흐릿한 논리 픽셀 대신 네이티브 해상도로 렌더링됩니다. 또한 매번 drawImage() 전에 ctx.clearRect()를 호출해 반투명 프레임이 잔상을 남기지 않도록 하세요.
네 — 그것이 바로 Astorie의 강점입니다. 하나의 노드 기반 캔버스에서 Kling, Seedance, Runway 같은 모델에 걸쳐 첫-끝 프레임 제어로 반복 가능한 AI 클립을 생성하고, 프레임을 추출·선별하며, 필요하면 주요 프레임을 4K로 업스케일한 뒤, GSAP에 쓸 깨끗한 WebP 시퀀스를 /public/frames 폴더로 내보낼 수 있습니다. 경쟁 튜토리얼은 이미 가지고 있는 비디오 파일에서 시작하지만, Astorie는 파이프라인의 업스트림인 생성-추출-선별 절반을 모두 담당합니다.