Communitygithub.com

다이내믹 줌 (Istok-Media-Tool/zoom)

사용자와 함께 plan-editor 도구로 줌 계획을 수립한 후, 이미 편집된 토킹헤드 영상에서 콘텐츠 중심의 순간에 다이내믹한 줌 인/아웃 크롭을 추가합니다. 피사체를 정확히 중앙에 배치하고 적절한 헤드룸을 유지하여 서로 다른 줌 레벨 간의 컷이 마치 의도적인 시네마틱 카메라 움직임처럼 느껴지도록 합니다.

zoom)란 무엇인가요?

이 스킬은 기존의 토킹헤드(인터뷰/강연 등) 영상에 대해 계획적이고 자연스러운 다이내믹 줌 효과를 적용하는 방법을 제공합니다. 사용자는 plan-editor 스킬을 통해 대본의 단어 단위로 줌 인/아웃 타이밍과 강도를 함께 결정합니다. 스킬은 각 세그먼트별로 피사체의 얼굴 위치를 정밀 측정하고, 9% 헤드룸을 유지하는 크롭을 계산하여 줌 간의 부자연스러운 점프를 방지합니다. 특히 '와이드' 샷도 동일한 기준으로 크롭하여 모든 컷이 하나의 카메라로 촬영된 듯한 일관성을 제공합니다. 실제 예제를 바탕으로 한 세밀한 보정 과정(예: 태블릿 같은 중요한 피사체 보호, 에너지 곡선에 따른 줌 계획 수립)이 포함되어 있어 전문 편집자의 감각을 재현합니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Istok-Media-Tool/video-edit-skills/tree/HEAD/video-edit/skills/zoom

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

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

문서

$SKILLS는 이 스킬 폴더를 포함하는 상위 폴더입니다. (스킬이 로드될 때 표시되는 기본 디렉토리의 부모, 즉 플러그인의 skills/ 폴더)

다이내믹 줌

이 스킬이 필요한 이유

이미 편집된 토킹헤드 영상에 '다이내믹' 줌을 추가하는 것은 숫자만 봐서는 드러나지 않고 실제 결과물을 봤을 때만 티가 나는 실수를 하기 쉽습니다. 실제 사례에서는 동일한 영상이 여러 번의 수정을 거쳐야 제대로 완성되었고, 각 수정 과정은 처음부터 다시 배울 가치가 없는 교훈을 남겼습니다:

  1. 모든 컷에 무턱대고 줌을 넣는 방식은 싸구려 같고 촌스러워 보입니다. 실제 편집자는 훨씬 간결하고 신중하게 선택합니다.

  2. 눈높이를 기준으로 대칭으로 크롭을 중앙에 맞추면 (실제 머리 위쪽을 기준으로 삼지 않으면) 확대 후 머리 위에 불필요한 공간이 많이 남습니다 (~9%가 적당했을 때 ~17%의 헤드룸이 생겼습니다).

  3. 하나의 세그먼트에서 측정한 머리 위치를 같은 녹화의 다른 세그먼트 크롭 계산에 재사용하면, 해당 세그먼트에서 피사체가 몇 분 전/후에 자리에서 움직였을 때 (같은 연속 녹화임에도 불구하고) 피사체가 눈에 띄게 중앙에서 벗어나 보입니다.

  4. 확대된 샷만 보정하고 '와이드'/미확대 샷은 전혀 크롭하지 않으면, 와이드 샷과 확대 샷 사이의 모든 컷에서 피사체가 옆으로 움찔하는 현상이 그대로 나타납니다. 와이드 샷도 동일한 센터링과 헤드룸 처리를 (훨씬 낮은 줌 레벨로) 해야 컷들이 하나의 일관된 카메라처럼 느껴집니다.

  5. 클립당 하나의 줌 레벨을 사용하고 각 문장의 중간 지점에 기계적으로 확대를 넣으면 사용자에게 무작위로 읽힙니다. 줌은 대본을 따라야 합니다. 모든 변화는 그것을 정당화하는 특정 단어나 구(공개, 핵심 용어, 개인적인 질문, 결정타)에 맞춰 떨어져야 하며, 당겨짐(pull-back)은 규모에 관한 단어('우주', '수십억 년')에 맞춰 떨어집니다. 변화는 클립 중간에, 때로는 클립 내에서 두 번 이상 발생합니다.

  6. 사용자는 plan-editor 도구(1단계)를 사용하여 단어 단위로 줌 계획을 함께 결정하기를 원합니다. 모든 대본에 대해 항상 사용하세요.

  7. 사용자가 선택한 줌 단계: 1.0x, 1.5x, 1.7x, 2.0x, 2.2x (계획 레벨 0-4). '1.0x'(레벨 0)는 RAW 프레임이 아닙니다. RAW 크롭되지 않은 프레임으로 렌더링하면 피사체가 중앙에서 벗어나고 머리 위 공간이 너무 많아 사용자가 다른 모든 샷과 동일한 헤드룸과 중앙 정렬을 요청했습니다. 따라서 레벨 0 = --baseline 크롭(9% 헤드룸을 충족하는 가장 느슨한 크롭, 재중앙 정렬, 실제로는 약 1.2x)입니다. build_zoom_selections.py가 자동으로 처리합니다. 도구에서 레이블을 '1,0x'로 유지하세요. '가장 넓고 중앙 정렬됨'을 의미합니다.

1단계: plan-editor 도구에서 함께 줌 계획 수립

모든 비디오에 필수: plan-editor 스킬 ($SKILLS/plan-editor/SKILL.md)을 로드하고 따르세요. 이것은 사용자가 줌을 함께 결정하고 'Izmeni tekst'를 통해 같은 문장의 캡션 텍스트를 수정하는 표준 클릭-단어 도구입니다. 사용자가 그 도구에서 만든 계획 없이 줌을 렌더링하지 마세요.

해당 단계에서 당신의 역할은 초기 제안을 구성하는 것입니다. 콘텐츠의 에너지 곡선을 기반으로 빌드하세요. 도입/차분 = 와이드, 반전이나 결정타는 해당 키워드에 밀착, 설명적인 비트는 숨을 고르기 위해 당겨짐, 클라이맥스는 가장 타이트한 프레임, 결정적인 레벨 차이(미세한 변화 금지), 오프닝 후 적어도 한 번은 와이드로의 실제 복귀. 그리고 각 변경 이유를 제시하세요. 사용자의 답변(PLAN_JSON, SENTENCES_JSON)이 확정된 계획입니다. 5단계에서 정확히 렌더링하세요.

계획을 적용하려면 단어 타임스탬프(5단계)와 클립별 피사체 위치(2단계)가 필요합니다.

examples/sample/에는 transpose=1로 회전된 측면 촬영 비디오에 대한 실제 확정 세트(문장, 사용자 계획, 위치, 컷)가 들어 있습니다. 파일 형식과 이 사용자가 선택하는 계획의 종류에 대한 참고 자료입니다.

2단계: 피사체 위치 측정 - 세그먼트별로, 항상

동일한 연속 녹화 내에서도 한 세그먼트에서 측정한 머리 위치를 다른 세그먼트의 크롭 계산에 절대 재사용하지 마세요. 긴 테이크에서 몇 분 더 진행되면 누군가 자리에서 움직일 수 있으며, 초기 세그먼트 프레임에서 계산된 크롭은 나중 세그먼트에서 피사체를 잘못 중앙에 배치하거나 더 타이트한 줌에서는 실제로 피사체를 잘라낼 수 있습니다(비슷해 보이지만 똑같이 구성되지는 않은 경우).

크롭할 각 세그먼트(와이드 샷 포함 - 4단계 참조)에 대해:

ffmpeg -nostdin -y -ss <해당_세그먼트_중간_타임스탬프> -i source.mp4 -frames:v 1 ref.png
ffmpeg -nostdin -y -i ref.png -vf "drawgrid=w=54:h=96:t=1:[email protected]" grid.png

(54x96은 1080x1920 프레임에서 20x20 그리드를 제공합니다. 다른 해상도에 맞게 비례적으로 조정하세요.) grid.png를 보고 다음을 확인하세요:

  • cx - 얼굴/코 중앙의 x 좌표
  • head_top_y - 머리카락 맨 위의 y 좌표 (눈이 아님 - 헤드룸은 머리 꼭대기에서 측정합니다)

3단계: 크롭 계산

python3 scripts/compute_crop.py --cx <측정_x> --head-top-y <측정_y> --zoom 1.7
python3 scripts/compute_crop.py --cx <측정_x> --head-top-y <측정_y> --baseline
  • --zoom Z 명시적 확대 레벨(예: 170% 샷의 경우 1.7)
  • --baseline '와이드' 샷용: 헤드룸 목표를 충족시키면서 수평으로 재중앙 정렬하는 가장 느슨한 크롭을 찾습니다. 이렇게 하면 와이드 샷이 약간만 크롭되지만(말 그대로 100%/건드리지 않은 것은 아님) 확대된 샷과 정확히 동일한 광학 중심과 헤드룸을 공유하게 되어 컷이 피사체가 움찔하는 것처럼 보이지 않게 합니다. 와이드 샷 크롭을 건너뛰지 마세요. 확대된 샷만 수정하고 와이드 샷을 RAW로 그대로 두는 것은 테스트에서 '뭔가 이상하다'는 느낌의 가장 큰 원인이었으며, 개별 줌 레벨을 잘못 설정하는 것보다 더 컸습니다.

기본 헤드룸은 크롭된 프레임 높이의 9%이며, 테스트에서 올바르게 읽혔습니다. 그 이하는 비좁아 보였고, 미적인 문제만이 아닙니다. 세로형 플랫폼(Instagram/TikTok/Shorts)은 프레임 맨 위에 사용자 이름/캡션/페이드 UI를 오버레이하므로, 그 여백은 플랫폼이 덮을 기능적 공간이지 여유 공간이 아닙니다. 특정 프로젝트에서 다른 값이 필요하면 --headroom으로 재정의하세요. 하지만 해당 플랫폼으로 보내는 콘텐츠의 경우 ~9% 이하로 너무 내려가지 마세요.

위 모든 사항에 대한 전체 설명은 scripts/compute_crop.py의 docstring에 있습니다. 짧으므로 특정 사례에서 명확하지 않은 부분이 있으면 읽어보세요.

4단계: 전체 렌더링 전에 실제 렌더링된 출력 확인

단일 스틸 프레임에서 올바르게 보이는 크롭이라도 움직임을 확인하면 피사체가 잘리거나 중앙에서 벗어날 수 있습니다. 각 세그먼트에 대해(특히 참조 프레임을 선택할 때 가장 많은 신체 움직임(몸을 앞으로 기울이기, 고개 기울이기)을 보였던 세그먼트):

  1. 해당 세그먼트 하나만 크롭을 적용하여 렌더링합니다.
  2. 측정한 프레임뿐만 아니라 실제 지속 시간 전반에 걸쳐 3개 이상의 프레임을 샘플링합니다. 예: ffmpeg -i clip.mp4 -vf fps=3 check_%02d.png.
  3. 확인합니다. 머리가 잘리거나 가장자리에 너무 붙어 있으면, 해당 세그먼트의 줌을 느슨하게 하거나 여백을 넓힌 후 진행합니다.

이 검사가 통과된 후에야 모든 세그먼트를 렌더링하고 연결합니다.

5단계: 확정된 계획을 단어 기반 컷으로 변환한 후 조립

  1. 동일한 컷의 렌더(일반 컷 또는 이전 줌 렌더 - 패딩이 일치해야 함)에서 단어 타임스탬프를 가져오고, 해당 렌더의 클립별 지속 시간을 기록합니다(작업 디렉토리에서 각 clip_NNN.mp4에 대해 ffprobe 실행):
    python3 scripts/word_times.py cut_render.mp4 words.json --language sr
    
  2. 선택 파일을 빌드합니다. cuts.txt = 기본 스킬의 패딩되지 않은 시작<TAB>끝 (클립별); pos.json = 2단계의 [[cx, head_top_y], ...] (클립별, --pre-filter가 회전하는 경우 회전된 프레임에서 측정):
    python3 scripts/build_zoom_selections.py --cuts cuts.txt --positions pos.json \
      --plan plan.json --sentences sentences.txt --words words.json \
      --durations 6.28,6.08,... [--pre-filter transpose=1] > selections.txt
    
    출력되는 일치 보고서를 읽으세요. 계획된 모든 단어는 exact(또는 합리적인 fuzzy~ 철자 변형 - Whisper는 이름을 다르게 철자하거나 키릴 문자로 표기할 수 있음)를 표시해야 합니다. proportional (no match!) 줄은 렌더링 전에 수동으로 수정해야 합니다. 변경은 단어 0.08초 전에 적용됩니다(--lead). 여러 레벨이 있는 클립은 하나의 연속 클립(분할 + 시간 오버레이)으로 렌더링되므로, 문장 중간의 줌이 컷이나 오디오 이음새를 추가하지 않습니다.
  3. 모든 줌 변화 전후 0.2초 지점의 프레임을 스팟 체크(4단계 논리: 머리 절단 없음, 변화가 명확히 보여야 함)한 후 렌더링:
scripts/cut_and_concat.sh "<소스_비디오>" selections.txt final_output.mp4 "$PWD/build"

작업 디렉토리를 절대 경로로 전달하세요. 상대 경로를 사용하면 최종 concat 단계에서 concat_list.txt를 찾지 못합니다.

렌더링된 클립 지속 시간은 이전 렌더와 수백분의 1초 정도 차이가 날 수 있습니다. 따라서 캡션(자막 스킬)이 이전 렌더에 대해 시간이 지정된 경우, 각 캡션을 클립의 새 시작 시간만큼 이동시킨 후 자막을 입히세요.

cut_and_concat.sh를 적용하기 전에 상단의 주석 블록을 읽으세요. 루프 내 stdin 도용, 페이드 아웃이 컷의 마지막 음절을 삼키는 문제, concat 전 클립을 개별적으로 재인코딩해야 하는 이유 등 ffmpeg/bash의 함정에 대해 문서화되어 있으며, 그렇지 않으면 조용히 재도입되기 쉽습니다.

베이스라인(와이드) 세그먼트와 두 개의 확대 샷을 혼합한 예제 선택 파일:

63.82	69.57	crop=908:1614:152:304,scale=1080:1920
80.37	85.90	crop=636:1130:288:348,scale=1080:1920
97.25	100.81	crop=908:1614:152:304,scale=1080:1920

소스에 회전/방향 수정도 필요한 경우, 이는 별도로 처리됩니다(항상 필요한 것은 아니므로 이 스킬의 일부가 아님). 해당되는 경우 각 줄의 필터 체인 앞에 합의된 회전 필터를 직접 추가하세요.

전달

마스터 출력이 도구의 파일 크기 제한을 초과하는 경우, 마스터를 압축하지 말고 보조 압축 복사본을 만드세요:

ffmpeg -nostdin -y -i final_output.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k final_output_share.mp4 -loglevel error

발표자가 보여주는 물체는 모든 줌에서 온전해야 함

실제 릴에서는 발표자가 옆에 있는 태블릿에서 사진을 보여줍니다. 얼굴을 중심으로 한 크롭은 태블릿의 왼쪽 가장자리를 잘라냅니다("태블릿이 이렇게 어디서도 잘리면 안 됩니다"). 렌더링 전에 비디오가 다루는 프레임 내의 모든 것(태블릿, 제품, 들어 올린 페이지)을 나열하고 그리드에서 상자를 측정하세요. 모든 줌 크롭은 해당 상자를 약 30px의 여유 공간과 함께 포함해야 합니다. 크롭의 x/y를 상자 쪽으로 이동하고(얼굴이 중앙에서 벗어나 최대 ~70%까지 위치할 수 있음), 얼굴이 가장자리에 닿을 경우 해당 변경에 대해 더 느슨한 레벨을 사용하세요(실제 릴에서는 1.7x -> 1.6x). 모든 줌 범위의 프레임 하나를 확인하여 둘 다 온전한지 확인하세요.

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

관련 스킬