짧은 로컬 영상을 Animated WebP로 변환하는 브라우저 도구.
현재는 Phase 1의 크기·프레임률 설정을 추가한 개발용 구현이다. 기본값은 전체 구간·원본 표시 크기·원본 프레임 유지다. 사용자가 프레임률을 선택했을 때만 프레임을 줄인다. 입력과 출력은 서버에 업로드하지 않는다.
Node.js 20.19 이상을 사용한다. 빌드된 libwebp WASM은 public/engine에 포함돼 있다.
npm ci
npm run dev표시된 로컬 주소에서 MP4/H.264 SDR 영상을 선택한다. 브라우저는 데스크톱 Chrome을 우선 검증했다. 다른 브라우저와 모바일은 출시 지원이 확정되지 않았다.
npm run build
npm run preview- 파일 선택·드래그 앤 드롭과 원본 미리보기
- WebCodecs 기반 순차 디코딩 및 Worker 처리
- libwebp 1.6.0 Animated WebP 인코딩·무한 반복
- 손실 품질 90 또는 무손실 실험 설정
- 긴 변 최대 640·480·320·256·160px 축소 설정과 원본 크기 유지
- 원본 프레임 유지 또는 30·24·20·15·10fps 기준의 수동 프레임 감소
- 옵션 변경 후 샘플 시험 인코딩으로 예상 용량과 참고 범위 표시
- 입력·변환 프레임 수, 해상도, 실제 용량과 원본 대비 비율 표시
- 누적 타임스탬프 반올림으로 밀리초 변환
- 출력 RIFF 구조·해상도·시간 검증
- 결과 미리보기·WebP 다운로드·검증 JSON 저장
- Worker 종료로 취소 및 재시도
10초·2,073,600화소·100MiB·1,000프레임 이하, 각 변 4,096px 이하라는 제한은 실험 보호 장치다. 측정으로 확정한 출시 한계가 아니다. 축소를 선택해도 원본 입력에 대한 제한은 유지한다. 10ms 이하 표시 시간, HDR, 정지 화면으로만 구성돼 인코더가 정지 WebP로 조립한 영상은 현재 실험에서 거부한다.
구간 선택·오류 세분화와 단계별 취소 검증은 남은 Phase 1 과업이다. 동기화 비교와 완전한 오프라인 사용은 포함하지 않는다.
크기는 종횡비를 유지하고 원본보다 확대하지 않는다. 프레임률 선택은 원본의 해당 시점 화면을 일정한 출력 시각에 샘플링하며, 같은 원본 프레임은 유지 시간을 합쳐 중복 추가하지 않는다. 느린 원본은 프레임을 늘리지 않고 원래 시간을 유지한다. 프레임 보간은 하지 않는다.
선택한 프레임을 의도적으로 제외하는 것과 디코딩 누락을 구분한다. 모든 원본 프레임의 시간과 디코딩 수를 검사하고, 선택 계획에 있는 프레임이 모두 인코더에 전달됐는지 확인한다. 전체 재생 길이는 유지하고, 수동 감소 모드에서 10ms 이하가 되는 마지막 출력 구간은 이전 프레임의 유지 시간에 합친다.
설정 변경 시 이전 결과와 다운로드를 제거해 현재 설정과 혼동하지 않게 한다. 새 파일을 열면 크기·프레임률은 원본 유지로 돌아간다.
File Browser의 일부 버전은 작은 파일이나 미리보기 크기 안에 드는 이미지를 원본으로 전달한다. 파일 용량 안내는 참고 정보이며 특정 설치의 애니메이션 재생을 보장하지 않는다. 자동 용량 목표나 품질 저하는 적용하지 않는다.
옵션 변경 후 700ms 대기하고 앞·중간·뒤의 연속 프레임을 최대 36개 시험 인코딩한다. 프레임당 평균 용량으로 전체 용량을 추정하며, 장면별 차이와 여유 계수를 반영한 참고 범위를 함께 표시한다. 범위는 통계적 신뢰구간이나 보장 상한이 아니다. 출력 계획이 36프레임 이하이면 전체를 시험 인코딩해 용량을 측정한다. 영상 검사는 모든 원본 프레임을 디코딩하므로 기기 작업 비용이 발생한다. 옵션·파일 변경과 본 변환 시작 시 이전 추정 Worker를 종료하며, 변환 버튼은 계산을 기다리지 않고 사용할 수 있다.
File → Mediabunny → WebCodecs → OffscreenCanvas → libwebp WASM → RIFF 검증 → Blob
native/encoder.c는 WebPAnimEncoder를 감싼 작은 C API다. 각 프레임의 RGBA를 전달한 뒤 프레임 자원을 해제한다. 전체 영상을 비압축 프레임 배열로 쌓지 않는다. Mediabunny의 디코더 선행 버퍼와 인코더의 내부 버퍼, 압축 출력은 별도로 메모리를 사용한다.
보고서의 WASM 힙 용량은 할당된 힙의 크기이며, 브라우저 전체 메모리 또는 실제 사용 중인 메모리의 최대값이 아니다. 보고서 시간은 측정 장치의 관측값이다.
검증용 영상은 직접 생성한 막대형 프레임 번호와 움직이는 도형으로 구성된다. 원본 타임스탬프는 ffprobe로 읽고, 출력은 Chrome ImageDecoder로 모든 프레임을 다시 디코딩해 확인한다. 무손실 출력은 디코딩 직후 RGBA와 해시를 비교한다. 이는 원본 압축 영상의 디코딩·색 변환까지 무손실이라는 뜻은 아니다.
npm test
# ffmpeg와 ffprobe 필요
npm run fixtures
# 별도 터미널에서 npm run dev 실행, 설치된 Google Chrome 필요
npm run test:browser
npm run test:settings
npm run test:estimates결과는 test-results/에 저장된다. 브라우저 검증은 워크스페이스 최상위 MP4도 로컬에서 테스트한다. 해당 입력과 생성 결과는 Git 추적 대상에서 제외한다. 타인의 영상은 배포 자산에 포함하지 않는다.
다른 포트나 빌드된 앱을 검증할 때:
LOOPIT_TEST_URL=http://127.0.0.1:4173 npm run test:browserEmscripten 4.0.15와 libwebp v1.6.0을 프로젝트 안에 설치한다. 전역 셸 설정은 변경하지 않는다.
mkdir -p .tools
git clone https://github.com/emscripten-core/emsdk.git .tools/emsdk
.tools/emsdk/emsdk install 4.0.15
.tools/emsdk/emsdk activate 4.0.15
git clone --depth 1 --branch v1.6.0 https://github.com/webmproject/libwebp.git .tools/libwebp
npm run build:wasm스크립트는 도구를 다운로드하거나 원본 버전을 자동 갱신하지 않는다. 빌드된 엔진의 최대 WASM 메모리는 512MiB다. 메모리 부족은 변환 실패로 처리한다. libwebp 라이선스는 public/engine/libwebp-LICENSE.txt에 포함했다.
현행화: 2026-10-02. 구현 상태와 남은 과업은 개발 현황을 기준으로 확인한다.
- [개발 현황](docs/planning/Loopit 개발 현황.md)
- [MVP 기획서](docs/planning/Loopit MVP 기획서.md)
- [기술 설계안](docs/planning/Loopit MVP 기술 설계안.md)
- Phase 0 검증 결과
- 크기·프레임률 설정 검증
- 예상 용량 정확도 검증
docs/planning/: Obsidian 기획·설계·페이즈·현황 문서와 이번 현행화 시점에 동기화한 사본
실제 출시 지원 범위, 화질 기본값과 메모리 한계는 후속 페이즈에서 확정한다.