Communitygithub.com

GetOccasion/demo-video-skill

데모 영상 제작 스킬: 실제 브라우저 상호작용, 지역 Kokoro 음성 해설, 자막이 포함된 웹 앱 기능 데모 영상을 제작합니다. 사용자가 앱 기능에 대한 데모 영상, 워크스루, 스크린캐스트, 기능 투어, 설명 영상을 원하거나 팀, 고객, 이해관계자에게 기능을 '보여주기' 위한 영상, 또는 녹화본에 음성 해설이나 자막을 요청할 때 사용합니다.

demo-video-skill란 무엇인가요?

이 스킬은 사용자의 웹 애플리케이션 기능을 실제 브라우저 상호작용, 음성 해설, 자막이 포함된 짧은 데모 영상으로 변환합니다. `agent-browser`가 브라우저 상호작용을 녹화하고, 로컬 Kokoro TTS 엔진이 음성 해설을 생성하며, `ffmpeg`와 Pillow가 영상을 조합합니다. 핵심 아이디어는 **음성 해설이 시간 기준이 된다**는 것입니다. 음성 해설을 먼저 렌더링하여 각 장면의 길이를 측정하고, 각 브라우저 동작을 해당 단어가 언급되는 시점에 맞춥니다. 이렇게 하면 녹화 속도 변화에 영향을 받지 않습니다. 이 스킬은 Claude Code, Cursor, Codex 등에서 사용할 수 있으며, 사용자가 데모 영상, 워크스루, 기능 투어, 설명 영상을 요청하거나 음성 해설이나 자막이 포함된 녹화를 원할 때 활성화됩니다. 전체 워크플로는 기능 이해, 데모 데이터 생성, 스크립트 작성, 음성 해설 렌더링, 사전 점검, 시나리오 녹화, 영상 조립, 최종 검토 단계로 구성됩니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add GetOccasion/demo-video-skill

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

데모 영상

기능을 짧은 영상으로 만듭니다: 실제 브라우저에서의 실제 상호작용, 화면에 보이는 내용을 설명하는 음성 해설, 그리고 자막. 모든 것이 로컬에서 실행됩니다: agent-browser가 녹화하고, Kokoro가 말하고, ffmpeg와 Pillow가 조립합니다.

이 모든 것을 가능하게 하는 한 가지 아이디어: 음성 해설이 시간 기준입니다. 음성 해설을 먼저 렌더링하고, 각 장면의 길이를 측정한 다음, 각 브라우저 동작을 그 동작에 대해 설명하는 단어에 맞춥니다. 절대 녹화본에 음성 해설을 맞추지 마세요. 녹화본의 속도는 변동하지만, WAV 파일은 그렇지 않습니다. 녹화 자체는 음성 해설을 기다리지 않습니다. 동작은 연속적으로 실행되고, 조립 과정에서 음성 해설이 더 많은 시간이 필요할 때마다 프레임을 고정합니다.

도구

이 스킬의 스크립트는 기본 디렉토리 아래의 scripts/에 있습니다. 기본 디렉토리는 스킬이 로드될 때 표시되는 "이 스킬의 기본 디렉토리"입니다. 예제 시나리오는 그곳의 references/에 있습니다. 각 셸 명령은 새로 시작되므로, 한 명령에서 내보낸 변수는 다음 명령에서 사라집니다. 변수가 필요한 모든 명령은 아래와 같이 export SCRIPTS=<base directory>/scripts DEMO=<demo directory>;로 시작해야 합니다. 다음은 사전 점검 예시입니다.

export SCRIPTS=<base directory>/scripts; bash $SCRIPTS/preflight.sh

이 명령은 agent-browser와 이를 위한 Chrome, libx264를 사용하는 ffmpeg/ffprobe, Kokoro의 Python 환경과 모델을 확인합니다. (사전 설치된 경우 DEMO_VIDEO_VENV가 설정됨) 또는 uv(시스템 Python과 관계없이 Python 3.12에서 Kokoro와 Pillow를 실행)와 Hugging Face 접근 권한이 필요합니다. 종료 코드가 0이 아니면 중단하세요. 사용자에게 무엇이 빠졌고 어떻게 설치해야 하는지 알리고, 스크립트, 데이터, 녹화를 시작하지 마세요. 다른 TTS 엔진, 레코더, 또는 무음 영상을 묻지 않고 대체하지 마세요. 결과물이 사용자가 요청한 것과 다를 수 있습니다. 경고(espeak-ng 없음, 시스템 폰트 없음)는 차단되지 않습니다. 언급만 하세요.

$SCRIPTS/python은 아래의 Python 스크립트를 스킬의 uv.lock이 고정한 환경에서 실행합니다. 시나리오도 $SCRIPTS를 읽습니다. 모든 작업 파일은 저장소 외부의 스크래치패드에 있는 하나의 데모 디렉토리(DEMO, 절대 경로)에 보관하세요. 저장소 내부에만 쓸 수 있거나 영상을 해당 저장소의 풀 리퀘스트에 첨부해야 하는 경우, git이 무시하는 폴더를 대신 사용하세요. tmp/demo-video/<name> (단, git check-ignore -q tmp/demo-video가 성공해야 함), 그렇지 않으면 무시된다고 확인된 다른 폴더를 사용하여 그 안의 어떤 것도 커밋되지 않도록 하세요. 묻지 않고 이를 위해 .gitignore를 편집하지 마세요.

워크플로

1. 기능 이해 및 스토리 선택

브랜치 차이, 계획 또는 PR 설명, 그리고 기능의 사용자 대상 카피(i18n 파일, 툴팁, 도움말 텍스트)를 읽으세요. 툴팁은 보통 열과 옵션을 다른 무엇보다 잘 정의합니다. 음성 해설은 숫자의 의미에 대해 툴팁과 단어 하나하나 일치해야 합니다.

주인공(해당 기능을 실제로 사용할 가상의 비즈니스)과 기능이 그들에게 답해주는 질문을 선택하세요. 모든 컨트롤을 둘러보는 것보다 하나의 구체적인 시나리오를 질문에서 답까지 따라가는 데모가 더 좋습니다.

영상을 변경하는 사항에 대해서만 사용자에게 물어보세요: 대상(고객 또는 팀), 목표 길이, 음성, 그리고 녹화 전에 대본을 읽어볼지 여부. 기본값: 고객, 약 3분, af_heart. 사용자가 이미 답했거나 무의미해진 질문은 건너뛰세요. 전체 과정을 중간 점검 없이 처음부터 끝까지 요청했거나, 주변에 없어 답변할 수 없는 사용자는 기본값을 적용하고 대본 검토를 건너니다.

2. 스토리를 전달하는 데모 데이터 생성

빈 데이터나 무작위 데이터는 기능이 고장난 것처럼 보이게 합니다. 화면의 각 요점을 명확하게 만드는 데이터를 구축하세요: 첫 화면에서 볼 수 있는 경계 사례, 옵션을 전환할 때 눈에 띄게 변하는 합계.

  • 격리하세요. 별도의 데이터베이스를 시드하고, 앱을 자체 포트에서 실행하세요. 사용자의 개발 데이터를 재사용하지 마세요. 먼저 프로젝트 자체의 관례(CLAUDE.md, 워크트리 및 개발 서버 가이드, 시드 디렉토리)를 찾으세요. 프로젝트에 시드 프레임워크가 있다면 일회성 스크립트 대신 재사용 가능한 산업별 데모 시드를 작성하세요(예: db/seeds/demos/bike_shop.rb). 그러면 다음 데모 비용이 들지 않습니다. 커밋하기 전에 물어보세요.
  • 모든 날짜를 오늘 기준으로 상대적으로 지정하여 데모가 쓸모 없어지지 않도록 하세요.
  • 과거 기록은 시간 여행이 필요합니다. 앱은 과거의 주문이나 예약 생성을 거부합니다. 각 레코드가 실제로 생성되었을 시간에 생성하세요(예: Rails travel_to). 여행한 시계를 무시하는 상태를 주의하세요: 여행 전에 로드된 인스턴스에 메모이제이션된 값, 생성 시 계산된 상태 열. travel 블록 내에서 이를 다시 로드하고 새로고침하세요.
  • 빈 스키마의 스냅샷 (예: createdb -T)을 찍어 수정 후 리시딩이 마이그레이션이 아닌 드롭 앤 복사가 되도록 하세요.
  • 그런 다음 페이지를 열고 언급할 모든 숫자를 확인하세요.

3. 대본 작성

DEMO/scenes.json을 작성하세요: [{"id": "01_intro", "text": "..."}, ...]. 아이디어당 하나의 장면, 10~15개 장면. ID는 재생 순서대로 정렬됩니다.

  • 길이: Kokoro는 분당 약 150단어를 말하며, 각 장면은 약 1~3초의 동작과 일시 중지를 추가합니다. 3분 동안 약 450단어를 작성하세요. 420단어는 2분 40초가 나왔습니다.
  • 보는 사람이 보고 있는 것을, 보는 순서대로 말하세요. 컨트롤은 화면에 표시된 레이블로 부르세요.
  • 귀로 듣기에 맞게 작성하세요: 짧은 문장, 이상하게 읽히는 숫자는 철자로 표기("이십오 퍼센트").
  • 코드에서와 마찬가지로 음성 해설에서도 프로젝트의 어휘 규칙(용어집, 예약어)을 따르세요.
  • 화면에 보이는 것을, 화면에 보일 때 이름을 지정하세요. 필터 값, 메뉴 옵션 또는 레코드가 보이기 전에 이름을 지정하지 마세요("Sam이 판매한 모든 것" 직원 목록이 아직 닫혀 있는 동안). 이름 앞에 메뉴를 여는 단어를 넣어 시나리오가 먼저 메뉴를 열고 그 옵션에 포인터를 놓을 수 있도록 하세요.
  • 기능 자체의 질문으로 시작하세요. 첫 문장은 이 기능만이 답할 수 있는 질문이어야 합니다. "이번 달에 얼마나 팔았을까?"는 인접한 보고서도 설명합니다. "어느 매장이 스타터 키트를 가장 많이 팔았을까?"는 그렇지 않습니다. 결정하기 전에 앱의 형제 기능(다른 보고서, 다른 설정 페이지)을 확인하세요.
  • 명시된 대상에 맞게 작성하세요. 고객 데모는 그들이 할 수 있는 것을 보여줍니다. 마케팅이나 계정 관리를 위한 내부 데모는 결정 방식, 활성화 방법, 한계, 그리고 고객이 질문할 때 답변할 내용도 다룹니다.

사용자가 대본 검토를 요청했다면, 오디오를 렌더링하기 전에 여기서 멈추고 보여주세요: 먼저 짧은 개요(장면당 한 줄: 무엇을 보여주는지와 그 장면이 전달하는 한 가지 주장)와 확신이 없는 주장, 그리고 원하는 사람을 위해 아래에 전체 텍스트를 포함합니다. 나중에 문장을 변경하면 해당 장면을 다시 렌더링하고, 시나리오를 다시 큐잉하고, 테이크를 다시 녹화해야 하므로 지금이 스토리를 변경하기에 가장 저렴한 시점입니다. 그렇지 않으면 계속 진행하세요. 최종 보고서에서 대본이 DEMO/scenes.json에 있다고 언급하세요.

4. 음성 해설 렌더링

export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/narrate.py $DEMO

단어별 타임스탬프가 포함된 tts/<id>.wav 및 tts/<id>.words.json을 쓰고 각 장면의 길이를 출력합니다. 단일 장면을 다시 렌더링하려면 --only <id> ...를 사용하세요. 음성: af_heart(가장 자연스러운 미국식), am_michael, bf_emma(영국식). 음성의 첫 글자가 언어를 선택합니다.

5. 모든 상호작용 사전 점검

녹화 전에 각 단계를 agent-browser로 한 번 실행하고 스크린샷을 찍으세요. 이렇게 하면 카메라 앞에서 무엇이 깨질지 찾을 수 있습니다: 잘못된 경로, 오버레이에 가려진 요소, 닫을 때 제출되는 메뉴, 배지가 나타날 때 레이아웃이 이동하는 경우. 스냅샷 참조(@e12)는 페이지를 로드할 때마다 변경되므로 시나리오에서 안정적인 CSS 선택자(id, dom_id, name=)를 사용하세요. demo.click_role보다 선호하세요: 헬퍼는 동작 전에 포인터를 선택자의 요소로 글라이드하지만, agent-browser는 역할에 대한 위치를 제공하지 않으므로 역할 클릭은 포인터를 점프하게 만듭니다.

개발 전용 오버레이(프로파일러, 디버그 바)를 숨기고, "Logged in as…"와 같은 일회성 플래시를 테이크가 시작되기 전에 페이지를 두 번 로드하여 제거하세요(demo.prime(url)).

6. 시나리오 작성 및 녹화

시나리오는 SCRIPTS/recorder.py를 사용하는 DEMO의 짧은 Python 스크립트입니다. references/example_scenario.py에서 시작하세요. 동작을 단어에 큐잉하려면 s.at("word")를 사용하세요. 구두점은 자체 토큰이므로 "October,"가 아닌 "October"에 큐잉하고, 단어가 두 번 나타나면 nth=를 사용하세요.

헬퍼를 통해서만 이동하세요. 원시 agent-browser 포인터 명령은 포인터를 순간이동시키고 마우스 휠은 페이지를 점프시켜 카메라에서 컷처럼 보입니다. demo.move, demo.hover, demo.click은 포인터를 그곳으로 글라이드시키고 도착하면 반환됩니다. demo.scroll은 스크롤을 애니메이션하고 페이지가 멈추면 반환됩니다. 따라서 모든 움직임은 다음 홀드가 프레임을 고정하기 전에 녹화본에 완전히 표시됩니다.

보는 사람이 모든 메뉴를 읽을 수 있게 하세요. 1초 안에 열리고 닫히는 목록은 아무것도 보여주지 않습니다. 열린 목록에 머물고 선택하기 전에 포인터를 옵션에 놓는 헬퍼를 사용하세요.

  • 사용자 정의 HTML 메뉴의 경우 demo.pick(open_trigger, option_selector).
  • 내레이터가 이름을 부를 때 정확히 옵션에 포인터를 놓으려면 두 헬퍼 중 하나에 scene=s, cue="<word>"를 전달하세요. 메뉴는 약 2초 전에 열립니다. 이를 위해서는 같은 장면 내에서 큐 단어 전에 약 2초의 음성 해설이 필요하며, 그 사이에 느린 동작이 없어야 합니다. 조립은 불가능할 때 warning: ... runs ...s late를 출력하며, 해결 방법은 대본에 있습니다("그 메뉴의 세 번째 선택은 분할합니다..." 대신 "또는 분할합니다...") 또는 느린 동작을 이전 장면으로 이동하는 것입니다.
  • 네이티브 <select>의 경우 demo.pick_native(select_selector, label). 헤드리스 녹화는 네이티브 select의 팝업을 그리지 않으므로, 이 헬퍼는 select의 실제 옵션에서 대체 목록을 그리고, 선택 항목을 가리킨 다음 선택합니다.

테이크당 하나의 뷰포트. 뷰포트가 녹화 중간에 변경되는 테이크는 왜곡되어 나옵니다. 전화 세그먼트는 자체 테이크로 녹화하세요(demo.viewport(390, 844)). 조립은 캔버스에 맞춥니다. 그 후 짧은 마무리 테이크가 데스크탑으로 돌아갑니다.

각 테이크를 녹화하세요: export SCRIPTS=… DEMO=…; python3 -I $DEMO/scenario.py desktop (그리고 phone, outro, ...). 녹화 시간은 측정되지 않습니다: s.at()과 s.until()은 sleep하지 않고 홀드 포인트를 기록하므로, 테이크는 동작 시간만큼만 실행되고(2분 영상이 1분 미만으로 녹화됨) 잘못된 선택자나 음성 해설에 없는 큐 단어는 몇 초 안에 실패합니다. 따라서 별도의 사전 실행이 필요 없습니다. 테이크를 직접 반복하세요. 각 테이크는 <take>.mp4와 <take>.holds.json을 씁니다. 조립은 음성 해설이 큐 단어에 도달할 때까지 각 홀드 포인트에서 프레임을 고정합니다. 테이크 중간에 단계가 실패하면 시나리오를 수정하고 전체 테이크를 다시 실행하세요. 홀드는 깨끗한 실행에서 나와야 합니다.

모든 동작은 다음 큐 전에 페이지가 안정되어야 합니다. 마지막 프레임이 고정되기 때문입니다. 다시 로드하거나 제출하는 메뉴 후에 time.sleep을 유지하세요. 그렇지 않으면 고정된 프레임이 반쯤 로드된 페이지를 보여줍니다. 자체 타이머로 실행되는 것(사라지는 토스트, 스피너, 카운트다운)은 시나리오가 작동하는 동안만 진행되므로 홀드는 중간에 고정됩니다. 이를 카메라에서 제외하려면, prime이 로그인 알림에 대해 하는 것처럼 다음 큐 전에 완료될 때까지 기다리세요. "저장됨" 토스트가 나타나고 사라지는 것과 같은 것을 보여주려면, 전체 효과를 포함하는 동작 후에 time.sleep을 넣어 다음 홀드 전에 재생되도록 하세요. 유휴 상태에서 지속적으로 애니메이션되는 페이지의 경우, DEMO_TIMED=1로 해당 테이크를 시간 측정하여 녹화하세요. 그러면 녹화 중 각 큐 단어까지 sleep하고 대신 <take>.timeline.json을 씁니다.

7. 조립

먼저 브라우저를 닫으세요(agent-browser close): Chrome과 ffmpeg의 인코더가 함께 작은 시스템 또는 컨테이너의 메모리를 초과할 수 있습니다.

export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/assemble.py $DEMO --takes desktop phone outro --out <name>

DEMO/build/<name>.mp4를 생성합니다. 내장 자막, 소프트 자막 트랙 및 음성 해설이 포함되며, <name>.srt도 생성됩니다.

8. 전달 전 검토

영상은 볼 수 있지만 들을 수는 없습니다. 확인할 수 있는 것을 확인하세요:

export DEMO=…; ffmpeg -v error -y -i $DEMO/build/<name>.mp4 -vf "fps=1/6,scale=480:-1,tile=4x8" -frames:v 1 $DEMO/build/grid.png   # 읽기
export DEMO=…; ffmpeg -i $DEMO/build/<name>.mp4 -map 0:a -af volumedetect -f null - 2>&1 | grep volume                              # 무음 아님

그리드에서 각 장면이 음성 해설에서 설명하는 내용을 보여주는지, 자막이 가리키는 대상을 가리지 않는지, 어떤 프레임도 반쯤 로드된 페이지나 오류를 잡아내지 않는지 확인하세요. 중요한 순간의 큐 시간에 단일 프레임을 캡처하세요(-ss <t>).

그런 다음 개발 서버를 중지하고 영상을 전달하세요: 사용자의 자신의 시스템에서는 사용자가 찾을 수 있는 위치에 복사하고 open하세요. 다른 곳에서는 경로를 제공하거나, 당신의 도구가 그렇게 한다면 풀 리퀘스트에 첨부하세요. 보고할 때 확인할 수 없었던 것(오디오는 귀로 확인)과 대체 메뉴와 같은 단축 방법을 나열하세요.

이미 해결된 함정

  • 페이지가 안정되는 동안 agent-browser click이 "covered by"로 실패합니다. recorder.ab는 재시도합니다. 재시도를 견디지 못하는 실패는 실제이므로 선택자를 수정하세요.
  • 닫힐 때 제출되는 메뉴(예: Escape에서 제출되는 체크리스트)는 다시 로드하는 데 몇 초가 걸립니다. 다음 클릭 전에 이를 기다리세요. 그렇지 않으면 클릭이 이동된 버튼에 떨어집니다.
  • Overmind는 오래된 .overmind.sock에서 시작을 거부합니다. 소켓에서 아무것도 응답하지 않는지 확인한 후에만(overmind status) 소켓을 제거하세요.
  • 긴 스크래치패드 경로는 Unix 소켓 제한을 초과합니다. 소켓을 그곳으로 지정하지 마세요.
  • Kokoro는 긴 텍스트를 청크로 나눕니다. narrate.py는 각 청크의 단어 타임스탬프를 오프셋하므로 추측한 초가 아닌 단어로 큐잉하세요.

관련 스킬