Communitygithub.com

Istok-Media-Tool/basic

원테이크 영상/오디오 녹화본(리테이크, 헛발, 실수로 가득한 단일 연속 촬영)을 Whisper 전사와 무음 감지를 교차 검증하여 프레임 정확도로 편집한 후 ffmpeg로 조립합니다.

basic란 무엇인가요?

이 스킬은 긴 단일 테이크 영상에서 불필요한 구간(침묵, 리테이크, 잘못된 시작)을 자동으로 제거하여 깔끔한 최종 편집본을 생성합니다. Whisper만으로는 정확한 컷 포인트를 얻기 어렵다는 점을 보완하여, 무음 감지(silence detection)로 실제 발화 경계를 찾고 각 버스트를 개별 전사함으로써 숨겨진 리테이크를 드러냅니다. macOS 및 Linux에서 사용 가능하며, ffmpeg와 Python 스크립트로 구성된 전체 워크플로우를 제공합니다. 사용자는 편집 계획을 확인한 후 최종 렌더링 전에 수정할 수 있으며, 결과물의 품질을 자동 검사하는 체커도 포함되어 있습니다.

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

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

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

문서

기본 비디오 편집 (원테이크를 대본에 맞게 자르기)

이 스킬이 존재하는 이유

리테이크로 가득 찬 단일 연속 촬영을 전사만으로 정확하게 편집하는 것은 생각보다 어렵습니다. 이 워크플로우를 처음 수동으로 수행했을 때, Whisper의 자체 세그먼트 타임스탬프를 신뢰하면 한 문장이 22초나 걸린 것처럼 나오는 편집 계획이 만들어졌습니다. 실제로는 5.75초였고, Whisper는 16초의 침묵과 두 번의 잘못된 시작을 하나의 세그먼트로 합쳐버렸습니다. 그 전사를 그대로 컷에 사용했다면 영상 곳곳에 데드 에어가 가득했을 것입니다.

이 교훈을 기억하세요: 이 워크플로우의 목표는 정확한 컷 포인트이며, Whisper만으로는 일시 정지, 리테이크 또는 반복된 대사가 있는 영상에서 신뢰할 수 있는 컷 포인트를 제공하지 않습니다. 아래의 모든 내용은 이를 보완하기 위해 존재합니다.

워크플로우

1. 소스 분석

비디오의 기본 정보를 먼저 확인합니다:

ffprobe -v error -show_entries format=duration,size -show_entries stream=width,height,codec_name,codec_type,avg_frame_rate -of default=noprint_wrappers=0 "<video>"

Whisper 전사를 위해 모노 16kHz 오디오를 추출합니다:

ffmpeg -y -i "<video>" -vn -ac 1 -ar 16000 audio.wav -loglevel error

사용자가 대본/시나리오 파일(txt, rtf, docx 등)을 제공했다면, 의도된 내용을 비교할 수 있도록 지금 읽으세요. .rtf 파일은 깨끗한 텍스트를 얻기 위해 textutil -convert txt -stdout file.rtf (macOS)가 필요한 경우가 많습니다.

2. 1차 전사로 대략적인 개요 파악

whisper audio.wav --language <lang> --model medium --output_format txt --output_format srt --output_dir .

이는 전체 내용과 각 대사가 녹음에서 대략 어느 위치에 있는지 파악하는 데만 사용하세요. 세그먼트 시작/종료 시간을 실제 컷 포인트로 사용하지 마십시오 – 위의 "이 스킬이 존재하는 이유"를 참조하세요. 녹음이 길다면, 이 단계는 3단계와 병렬로 실행하거나 생략할 수 있습니다.

3. 무음 감지로 실제 발화 경계 찾기

실제로 중요한 단계입니다. 다음을 실행하세요:

python3 scripts/detect_bursts.py audio.wav > bursts.txt

이는 샘플 정확도의 발화 "버스트"를 제공합니다 (감지된 무음의 보완). 0.5초 미만의 짧은 블립은 거의 항상 호흡 소음이므로 제외됩니다. 이 타임스탬프를 신뢰하고 Whisper의 타임스탬프는 신뢰하지 마세요.

규모 확인: 자연스러운 말은 초당 약 2-3단어입니다. 버스트 지속 시간이 예상 텍스트 양에 비해 지나치게 길거나 짧다면 문제가 있는 것입니다.

4. 각 버스트를 개별 전사

python3 scripts/transcribe_bursts.py audio.wav bursts.txt --language <lang> --model medium

이는 Whisper 모델을 한 번 로드한 후, 각 버스트를 condition_on_previous_text=False 및 temperature=0 설정으로 자체 짧은 클립으로 전사합니다. 이 조합이 숨겨진 리테이크를 실제로 드러냅니다: 전체 파일 패스에서는 하나의 깨끗한 대사로 레이블링되던 버스트가, 분리되면 잘못된 시작 직후 실제 전달이 이어지는 경우가 있음 – 첫 번째 패스 전사에서는 보이지 않던 내용입니다.

눈에 띄는 리테이크가 있는 녹음은 스크립트 줄보다 더 많은 버스트를 생성할 것으로 예상하세요 – 특히 화자가 가장 어려워한 대사에서는 한 줄에 여러 개의 잘못된 시작 조각이 나타납니다. 정상입니다; 이 단계의 목적이 바로 이것입니다.

5. 편집 계획 수립 – 자르기 전에 반드시 확인 받기

전사된 버스트를 대본의 각 줄/아이디어별로 그룹화합니다. 각 줄에 대해 일반적으로 다음 중 하나가 나타납니다:

  • 하나의 깨끗하고 완전한 버스트 – 간단합니다. 그 테이크를 사용하세요.
  • 잘못된 시작 후 완전한 버스트 – 완전한 버스트를 사용하고 조각은 폐기합니다.
  • 같은 표현의 여러 완전한 테이크 – 실제 성능 복제본입니다; 전사만으로 전달/감정을 판단할 수 없으므로 기본값(예: 나중 시도, 사람들이 종종 안정된 테이크를 찾는 경향)으로 자르고 사용자가 Dublovi 모드(아래)에서 선택하도록 합니다.
  • 실제로 다른 표현의 여러 완전한 테이크 (화자가 매번 다르게 즉흥 연기) – 이는 성능 선택 이상의 실제 콘텐츠 선택입니다. 사용자에게 명시적으로 대안을 제시하고, 조용히 선택하지 마십시오 – 사용자는 스크립트보다 자신의 즉흥 구문을 선호할 수도 있고 그 반대일 수도 있습니다.

두 종류의 중복 테이크 모두, 사용자가 직접 시청하여 결정합니다: 테이크는 계획 편집기의 Dublovi 모드(두 테이크를 나란히, 왼쪽/오른쪽으로, "Ovaj je bolji")에 들어가며, 모든 비디오에 대해 동일합니다. 채팅에서 테이크를 설명하지 말고 "dubl 1 ili 2"라고 묻지 마세요. 기본 선택으로 자르고, 모든 중복을 takes_src.json에 나열한 후 (plan-editor 스킬, "Dublovi") 편집기를 엽니다; 사용자가 다른 테이크를 선택하면 그 테이크로 교체합니다.

그런 다음 전체 계획을 사용자에게 메시지로 작성하세요: 각 줄에 대해 유지되는 정확한 텍스트, 정확한 타임스탬프 범위, 그리고 무엇이 왜 잘리는지(데드 에어, 잘못된 시작, 폐기된 대체 테이크)를 명시합니다. 자르기 명령을 실행하기 전에 명시적 확인을 기다리세요. 이는 형식적인 절차가 아닙니다 – 비디오 편집은 파괴적인 느낌이 들며 출력물을 보지 않고는 확인하기 어렵기 때문에, 사용자가 렌더링 시간을 소비하기 전에 계획을 보고 문제(잘못된 테이크 선택, 자연스럽지 않은 이어짐)를 발견할 수 있어야 합니다.

사용자가 총 길이를 묻는다면, 유지되는 세그먼트 길이의 합계를 계산하되 – 실제 최종 길이는 컷 사이에 패딩이 추가되면 약간 더 길어짐을 알려주세요(6단계).

6. 자르기 및 조립

확인된 선택 사항을 파일에 기록한 후 (각 줄에 대해 하나의 start<TAB>end 쌍, 출력 순서대로):

scripts/cut_and_concat.sh "<source_video>" selections.txt final_output.mp4

cut_and_concat.sh를 수동으로 실행하거나 수정하기 전에 상단의 주석 블록을 읽으세요 – ffmpeg가 bash 루프에서 stdin을 가로채는 문제, 페이드 아웃이 단어의 마지막 음절을 자르는 문제, 각 클립을 연결 전에 개별 재인코딩해야 하는 이유 등 스크립트를 그대로 사용하지 않고 처음부터 다시 작성하면 조용히 재발할 수 있는 문제점들이 문서화되어 있습니다.

기본값: 각 컷인 전 0.15초 패딩, 각 컷아웃 후 0.35초 패딩, 5ms 페이드인만(페이드아웃 없음), 각 클립을 재인코딩된 h264(crf 18) + aac 192k로, 각 클립을 정확한 프레임 수로 자르고 오디오도 동일한 길이로 트림한 후 ffmpeg concat FILTER(concat demuxer 사용 금지 – 아래 "모든 렌더링 확인" 참조)로 연결합니다. 특정 컷에 다른 처리가 필요한 경우(예: 호흡 소음이 많은 대사는 더 큰 POST_PAD 필요) PRE_PAD, POST_PAD, CRF, FADE_IN 환경 변수로 재정의할 수 있습니다.

첫 번째 패스 후 문장의 마지막 글자가 잘린 것처럼 들리면, 거의 항상 해당 특정 컷에 대해 POST_PAD가 너무 빡빡하기 때문입니다(화자의 마지막 자음이 무음 감지기의 임계값보다 더 길게 지속됨) – 페이드를 적용하는 대신 해당 세그먼트의 POST_PAD를 늘리세요. 페이드는 빡빡한 컷을 더 나쁘게 만들 뿐입니다.

빠른 페이스 – 사용자의 기준

기본 패딩(0.15s / 0.35s)은 모든 컷에 약 0.5초의 일시 정지를 남깁니다. 사용자는 이것이 리듬을 무너뜨린다고 생각했습니다("모든 것이 더 빡빡하고 매끄러워야 함"). 따라서 사용자의 비디오에서는 조립 전에 모든 컷을 더 타이트하게 만듭니다:

python3 scripts/tighten_cuts.py audio.wav selections.txt tight.txt
PRE_PAD=0.04 POST_PAD=0.08 scripts/cut_and_concat.sh "<source_video>" tight.txt final_output.mp4 <abs workdir>

이는 각 가장자리를 실제 발화가 시작/종료되는 지점(-42 dB)으로 이동시키고, 0.30초 이상의 내부 일시 정지를 분할합니다. 실제 영상에서는 1:48에서 1:41로 줄어들었으며 모든 단어는 그대로 유지되었습니다. 이후 결과를 전사하여 확인하세요: 유지된 텍스트의 모든 단어가 여전히 있어야 합니다. 이전 컷에 배치된 항목(클로즈업, 자막, 타이틀)의 타이밍은 일정한 shift가 아닌 소스 시간을 통해 다시 매핑되어야 합니다.

별도로 잘린 두 부분(예: 메인 + 아웃트로)이 결합될 때는, 첫 번째 부분의 실제 프레임 수(nb_read_frames / fps)에서 결합 지점을 가져오고 컨테이너 길이는 사용하지 마세요: 실제 영상에서 0.04초 차이로 인해 클로즈업과 아웃트로 사이에 와이드 샷의 원치 않는 프레임 하나가 남았고, 아웃트로의 첫 번째 자막이 한 프레임 일찍 나타났습니다. 결합 지점에서 끝나는 오버레이는 해당 프레임까지 실행되어야 합니다.

모든 렌더링 확인 – 전송 전에

실제 영상에서 사용자가 1:05 지점에서 와이드 샷의 원치 않는 프레임 하나를 발견했습니다("그런 일이 있어서는 안 됩니다"). 실제 원인은 더 컸습니다: 이전 cut_and_concat.sh는 concat demuxer로 클립을 연결했는데, 각 클립을 실제 프레임이 아닌 컨테이너 길이와 AAC 프라이밍/패딩에 따라 배치했습니다. 이로 인해 10개의 컷에서 80ms 구멍(2프레임)이 발생했습니다 – 재인코딩 후 화면에 정지 프레임이 나타나고, 오디오가 영상보다 점점 빨라져서 모든 오버레이와 자막이 한두 프레임씩 어긋났습니다. 수정된 스크립트(헤더의 gotcha #4)는 각 클립을 정확한 프레임 수로 자르고, 오디오를 정확히 그 길이로 패딩/트림한 후 concat 필터로 연결합니다.

이로부터 따르는 규칙:

  • 모든 렌더링(컷, 줌, 자막, 최종)에 대해 사용자에게 보내기 전에 체커를 실행하세요:

    python3 scripts/check_video.py final.mp4 --ass captions.ass --edges edges.txt --cuts joins.txt
    

    joins.txt = 최종 타임라인의 컷 리스트에 있는 모든 조인 지점 (각 부분의 durs.txt의 누적 합계와 부분 경계). 항상 전달하세요: 같은 프레이밍 내의 점프 컷은 장면 임계값보다 훨씬 낮은 점수를 받기 때문에, 체커가 실제 영상(1:15)에서 조인 3프레임 전에 발생한 줌 변경을 놓쳤습니다 – 사용자가 본 3프레임 플래시.

    이는 타임스탬프 구멍, 고립된 정지 프레임, 오디오 대 비디오 길이, 몇 프레임만 지속되는 샷, 0.4초보다 짧거나 사진 컷에서 1-3프레임 차이로 시작/종료되는 자막, 그리고 컷에서 2-5프레임 떨어진 오버레이 가장자리(edges.txt: 한 줄에 하나의 클로즈업 / B-roll 시작 또는 종료, 초 단위)를 보고합니다. 종료 코드 1 = 문제 발견됨: 수정하고 다시 렌더링하세요. "OK - nothing found"만 전송됩니다.

  • 모든 클립 위치는 프레임 수(nb_frames / fps 또는 스크립트가 작성하는 durs.txt)에서 가져오고, format=duration에서는 가져오지 마세요.

  • 컷이 변경될 때, 클로즈업, 자막 및 타이틀을 SOURCE 시간(이전 타임라인 -> 소스 -> 새 타임라인)을 통해 이동시키고, 상수 shift를 사용하지 마세요.

  • 컷에서 끝나는 오버레이(아웃트로 전의 클로즈업)는 정확히 해당 컷의 프레임까지 실행됩니다. 자막은 해당 클립과 함께 종료됩니다.

  • 조인 지점에서 6프레임 이내에 있는 모든 줌 변경, 클로즈업 종료 및 자막 가장자리를 해당 조인 지점에 스냅하세요(조인 직전에만 클로즈업 시작). 몇 프레임 떨어진 두 개의 사진 변경은 항상 결함으로 읽힙니다. 한 프레임에서는 하나의 컷으로 읽힙니다.

  • 마지막 세그먼트에 작은 꼬리(약 0.5초)를 주어 마지막 자막과 단어가 잘리지 않도록 하세요.

7. 전달

cut_and_concat.sh의 마스터 출력은 전체 품질입니다(crf 18로 재인코딩). 파일 크기 제한이 있는 도구를 통해 전달해야 하는 경우, 마스터를 압축하지 말고 이 목적을 위한 2차 압축 복사본을 만드세요:

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

사용자가 다이나믹스용 펀치인/줌 크롭도 원한다면, zoom 스킬을 참조하세요 – 이 스킬의 출력을 가져와 다시 자르거나, 더 효율적으로는 zoom 스킬의 세그먼트별 필터 체인을 cut_and_concat.sh를 한 번 실행하기 전에 selections.txt의 세 번째 열에 직접 작성하여 한 번에 결합할 수 있습니다. 필요한 경우 수직 회전은 별도로 처리됩니다(어느 스킬에도 속하지 않음) – 항상 원하는 것은 아니므로 사용자에게 문의하세요.

플랫폼 참고 사항

  • macOS에는 기본적으로 GNU timeout이 없습니다 – ffmpeg 호출을 timeout으로 감싸지 마세요. 짧은 클립의 경우 빠르며, 호출 도구의 자체 백그라운드/타임아웃 처리가 2단계의 긴 전체 파일 패스를 처리합니다.
  • while read ...; done < file 루프를 직접 작성하고(번들 스크립트 대신) 내부에서 ffmpeg를 호출하는 경우, ffmpeg 호출에 -nostdin을 추가하고 stdin을 /dev/null로 리디렉션하세요 – 그렇지 않으면 ffmpeg가 루프의 read를 위한 라인을 소비하여 명확한 오류 없이 각 반복에 적용되는 타임스탬프가 손상됩니다.

컷 후: 줌과 자막은 plan-editor를 통해

컷 비디오에 줌/펀치인(zoom 스킬) 및/또는 자막(subtitle 스킬)도 적용될 경우, 사용자의 표준은 plan-editor 스킬입니다: 사용자가 줌을 함께 결정하고 동일한 문장에서 자막 텍스트를 수정할 수 있는("Izmeni tekst") 하나의 대화형 단어 클릭 도구입니다. 이 스킬의 계획에서 클립당 유지된 텍스트를 유지하세요 – 이는 해당 도구의 sentences.txt가 됩니다(컷 클립당 한 줄, selections.txt와 동일한 순서).

Individual skills in this repo

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

관련 스킬