팟캐스트 편집 및 마스터링
사용자가 직접 녹음한 팟캐스트, 음성 녹음을 방송용 최종 파일로 만듭니다: 노이즈 제거, 앞뒤 무음 제거, 보컬 이퀄라이징 및 압축, 라우드니스를 약 -16 LUFS로 노멀라이제이션한 후 MP3로 내보냅니다. 필요하다면 오프닝/엔딩 음악 및 구간별 배경 음악을 추가하고, 편집 제안(긴 일시 정지를 줄일 수 있는 부분, 재녹음 구간)을 제공하며, 사용자가 확인한 후에만 오디오를 자릅니다.
아래의 scripts/, references/는 이 스킬 디렉토리 내에 있으며, 실행 시 전체 경로로 작성합니다. 예: bash <스킬 디렉토리>/scripts/edit_podcast.sh
1. 시작하기 전에 사용자에게 설명할 사항
- 먼저 ffmpeg를 설치해야 합니다 (무료 오픈소스 오디오 도구). 설치되어 있지 않으면 사용자에게 설치를 요청하세요: macOS는
brew install ffmpeg, Linux는 시스템 패키지 관리자를 사용합니다(예:apt install ffmpeg). 스크립트는 다음 순서로 ffmpeg를 찾습니다: 환경 변수FFMPEG→ 시스템 PATH의ffmpeg→ 일반적인 설치 위치/opt/homebrew/bin,/usr/local/bin. 6.0 및 8.1 버전에서 테스트 완료. - bash와 Python 3.9 이상이 필요하며, 표준 라이브러리만 사용하고 추가 Python 패키지 설치는 필요하지 않습니다.
- macOS와 Linux만 지원: macOS에서 실제 테스트 완료; Linux에서도 사용 가능; Windows는 WSL이 필요하며 테스트되지 않았습니다.
- 최초 노이즈 제거 시 약 300KB의 공개 노이즈 제거 모델(RNNoise) 을 인터넷에서 다운로드하여
~/.cache/ethan-podcast-post/에 저장하며, 다운로드 후 SHA-256으로 검증합니다. 다운로드 불가능하거나 검증에 실패하면 ffmpeg 내장 afftdn 노이즈 제거를 자동으로 사용하며, 스크립트가 어떤 방식을 사용하는지 출력하므로 이 점을 사용자에게 알려야 합니다. 모델 출처는references/third-party.md를 참조하세요. - 모든 처리는 로컬 머신에서 이루어짐: 오디오를 업로드하지 않으며, 요금이 부과되지 않습니다.
- 하지 않는 일: 사람 음성 생성 또는 복제, 성우, 전사(STT)(자막이 필요한 경우 사용자가 SRT 자막 파일을 제공해야 함), 어떤 플랫폼에도 게시하지 않으며, 음악도 제공하지 않습니다(배경 음악은 사용자가 직접 준비하고 사용 권한을 확인해야 함).
2. 파일 안전 (반드시 준수)
- 원본 녹음은 읽기 전용: 스크립트는 읽기만 하고, 수정, 이동, 삭제하지 않습니다.
- 출력은 항상 새 디렉토리에 저장: 녹음 파일 옆에 새 디렉토리를 만듭니다(예:
에피소드3_후반작업/). 녹음 파일이나 배경 음악과 같은 디렉토리에 저장하지 마십시오. 스크립트는 출력 디렉토리가 입력 파일과 동일한 디렉토리에 있으면 중단됩니다. - 임의로 덮어쓰지 않음: 출력 디렉토리에 이미 최종 파일,
audio_raw.wav,voice_master.wav가 있으면 스크립트는 덮어쓰지 않고 중단됩니다. 덮어쓰려면 먼저 사용자에게 어떤 파일이 덮어쓰기될 것인지, 덮어쓰면 이전 파일을 복구할 수 없음을 알리고, 사용자가 동의하면OVERWRITE=1을 추가하여 다시 실행합니다. 편집 제안, 컷 및 보컬 처리 스크립트도 마찬가지로 동일한 이름의 출력 파일이 있으면 중단되고, 사용자가 동의한 후에만--overwrite를 추가할 수 있습니다. - 삭제 전 반드시 확인: 어떤 삭제(
audio_raw.wav와 같은 큰 파일 정리 포함)도 먼저 사용자에게 어떤 파일을 삭제하는지, 이유를 설명하고 동의를 얻은 후에 삭제합니다. 스크립트의 중간 파일은 출력 디렉토리 내 현재 실행 전용 임시 서브디렉토리에 저장되며, 종료 시 이 서브디렉토리만 정리합니다. - 사용자가 자를 내용을 결정: 편집 제안은 후보 목록일 뿐이며, 사용자가 확인하기 전에는 자르지 않습니다.
3. 처리 흐름
먼저 환경 검사를 한 번 실행하고 결과를 사용자에게 알려줍니다:
bash <스킬 디렉토리>/scripts/edit_podcast.sh --check
이 명령은 찾은 ffmpeg의 위치, 버전, 250Hz 동적 EQ 활성화 여부(6절 참조), 노이즈 제거 모델 준비 상태를 출력합니다. ffmpeg를 찾을 수 없으면 설치 방법을 안내하므로, 먼저 설치한 후 계속 진행합니다.
1단계: 1차 처리, 보컬 마스터 및 미리듣기 버전 생성
KEEP_MASTER=1 bash <스킬 디렉토리>/scripts/edit_podcast.sh <녹음 파일> <출력 디렉토리>/pass1 <이름>
- 녹음 파일은 wav, m4a, mp3, mp4 등 ffmpeg가 읽을 수 있는 형식이어야 합니다. 듀얼 또는 멀티 트랙 대화인 경우
INTERVIEW_MODE=1을 추가합니다(압축 강도 증가, 여러 사람의 볼륨 차이 자동 평준화). - 처리 순서: 48kHz WAV로 추출 → 품질 검사 → 앞뒤 무음 제거(중간의 일시 정지는 건드리지 않음) → 입술/클릭 노이즈 제거 → 80Hz 하이패스 → 노이즈 제거 → 250Hz 동적 EQ → 디에서 → 컴프레션 → 5kHz 브라이트닝 → -16 LUFS로 선형 게인 조정 → 리미터 → MP3 내보내기 → 라우드니스 재측정.
- 결과물:
<이름>_final.mp3: 배경 음악이 없는 최종 파일(미리듣기 버전)voice_master.wav: 보컬 마스터(앞뒤 무음 제거 완료). 이후 전사, 편집 제안, 컷의 기준이 됩니다.audio_raw.wav: 원본 녹음을 WAV로 변환한 복사본(모든 처리가 성공한 후에만 출력 디렉토리에 기록됨). 이후 재처리에 사용 가능하며, 파일 크기가 큽니다.
- 출력에서 다음 항목을 확인하여 사용자에게 알려줍니다:
- 끝부분 볼륨이 -80dB 미만: 녹음이 중간에 끊겼을 수 있으므로 사용자와 먼저 확인
- 노이즈 제거 방식: RNNoise 또는 afftdn
- 최종 파일의 라우드니스는 -16 LUFS ±1 이내여야 하며, 실제 피크는 -1.5 dBTP를 초과하지 않아야 합니다. 벗어날 경우 스크립트가 알림을 출력하므로 그대로 사용자에게 알려줍니다.
- 녹음이 너무 작거나 빈 녹음인 경우 스크립트는 오류를 내고 중단되며, 빈 최종 파일을 생성하지 않습니다. 오류 메시지는 녹음 피크 값을 기반으로 권장 값을 제공합니다: 먼저 사용자에게 녹음에 실제로 말하는 내용이 있는지 확인하고, 지시에 따라
INPUT_GAIN=<권장 값>(단위 dB, 현재 설정된 INPUT_GAIN에 추가됨)을 추가하여 전체 구간을 정상 레벨로 올린 후 다시 실행합니다. 오류 발생 시 출력 디렉토리에 파일이 남지 않으므로 같은 디렉토리를 사용하여 다시 실행할 수 있습니다. 또한 사용자에게 향후 녹음 시 입력 볼륨을 높일 것을 권장합니다.
2단계: 편집 제안 (후보 목록만 제공, 오디오 수정 안 함)
자막은 1단계의 voice_master.wav에서 전사해야 합니다 (또는 1단계에서 배경 음악이 없는 최종 MP3를 전사, 두 파일의 타임라인이 동일). 원본 녹음의 전사를 사용하지 마세요: 원본 녹음은 앞뒤가 트리밍되지 않아 시간이 맞지 않아 찾은 위치가 틀어집니다.
줄일 수 있는 긴 일시 정지 찾기:
python3 <스킬 디렉토리>/scripts/edit_cuts.py suggest <출력 디렉토리>/pass1/voice_master.wav \
[--srt 자막.srt] [--protect 15:00.0-15:03.0 ...]
- 기본적으로 1.5초 이상의 일시 정지를 후보로 나열하며, 1.2초로 줄일 것을 제안합니다(일시 정지 양쪽에 각각 절반을 남겨 방의 배경 노이즈 유지).
- 사용자가 의도적으로 남겨둔 일시 정지(예: 중요한 말 이후의 침묵)는
--protect 시작-끝으로 표시하면 앞뒤로 0.5초 버퍼를 남겨 제안 대상에서 제외됩니다. - 명백히 긴 일시 정지가 발견되지 않으면 배경 노이즈가 높은 것이므로
--noise의 기본값 -45를 -40으로 조정하여 다시 실행합니다. - 40분 기준 후보가 15개 초과 또는 누적 60초 초과 시 스크립트는 매개변수가 너무 빡빡하다고 알리므로
--min-pause를 늘리고 모든 것을 그대로 자르지 마십시오.
재녹음 명령으로 표시된 재녹음 구간 찾기 (자막 필요):
python3 <스킬 디렉토리>/scripts/edit_cuts.py retakes <출력 디렉토리>/pass1/voice_master.wav --srt 자막.srt [--phrase 재녹음명령]
- 녹음 시 약속: 실수 → 1초 이상 멈춤 → 조용히 재녹음 명령을 한 번 말함 → 1초 이상 멈춤 → 전체 문장 처음부터 다시 말하기.
- 기본 명령은 '재녹음'입니다. 자신의 명령으로 변경하려면
--phrase를 사용합니다. 전사 소프트웨어가 명령을 동음이의어로 적는 경우 여러 번--phrase를 사용하여 여러 표기를 추가할 수 있습니다. 명령은 본문에 나타나지 않는 구절을 선택하는 것이 좋으며, 두 번 연속으로 말하면 오판 가능성이 더 낮아집니다. - 스크립트는 자막에서 명령을 찾고, 다시 말하는 문장의 시작을 사용하여 앞서 잘못 말한 문장과 매칭하여 컷 구간과 확신 수준(high / mid / low)을 제공합니다. low의 경우 반드시 사용자가 들어보고 확인해야 합니다.
두 명령은 각각 voice_master_suggested_cuts.tsv와 voice_master_retake_cuts.tsv를 생성합니다(이미 존재하면 덮어쓰지 않고, 사용자가 동의한 후 --overwrite를 추가). 후보를 사용자에게 하나씩 설명하고(시간, 전후 문맥, 절약 가능 시간), 사용자가 자를 항목을 확인한 후 해당 줄을 새 cuts.tsv 파일에 복사합니다(tab 구분: 시작, 끝, 비고; 시간은 MM:SS.s 형식).
3단계: 컷 적용
python3 <스킬 디렉토리>/scripts/edit_cuts.py apply <출력 디렉토리>/pass1/voice_master.wav --cuts cuts.tsv [--srt 자막.srt]
- 샘플 정밀도로 자르고, 접합부에 15밀리초 등전력 크로스페이드를 적용하여
voice_master_edited.wav와 컷 맵voice_master_edited.edit_map.json(각 보존 구간의 출처와 위치 기록)을 출력합니다. --srt가 제공될 경우, 완전히 보존된 자막은 함께 이동하고, 자막이 절반만 잘린 경우 스크립트는 실행을 거부합니다 —--srt를 제거하고 다시 실행하면, 자른 후 사용자에게 컷된 마스터를 다시 전사하도록 요청합니다.- 컷 후 사용자에게 각 접합부 전후 몇 초를 들어보도록 요청합니다: 딱딱거리는 소리 없이, 두 번의 흡음 없이, 음색 변화가 없어야 합니다.
4단계 (선택 사항): 인용, 회상 등 구간에 다른 음색 적용
python3 <스킬 디렉토리>/scripts/voice_fx.py <컷된 마스터.wav> --fx fx.tsv
python3 <스킬 디렉토리>/scripts/voice_fx.py --list # 5가지 프리셋 보기
- 프리셋: quote(편지, 다른 사람의 글 읽기), archive(오래된 신문, 공문), telephone(전화 통화 인용), memory(회상, 꿈), radio(오래된 방송, 한 에피소드에 최대 한 번).
- fx.tsv 각 줄: 프리셋, 시작, 끝, 페이드인, 페이드아웃, 비고(tab 구분, 페이드인/아웃을 '-'로 쓰면 기본값 사용).
- 처리 후 길이는 샘플 정밀도와 라우드니스가 동일하며, 출력 파일 이름은
<원본 이름>_fx.wav입니다.
5단계 (선택 사항): 배경 음악 및 CUE 시트
사용자가 배경 음악을 제공한 경우에만 수행합니다. 먼저 CUE 시트를 작성한 후 사전 검사합니다. CUE 시트는 2차 출력 디렉토리에 저장하며, 파일 이름은 반드시 <이름>_cues.tsv여야 합니다.
CUE 시트: tab으로 구분된 7열, #로 시작하는 줄은 무시, 특정 열에 '-'를 쓰면 기본값 사용. time은 MM:SS.s 형식으로, 보컬 마스터의 타임라인을 기준으로 합니다. 아래 시간은 예시입니다:
# type time src dur vol fade_in fade_out
stinger 02:00.0 - 3 - - -
break 12:00.0 -@20 8 0.5 2 2
bed 18:00.0 -@40 30 0.08 - -
ending 40:00.0 -@90 - - - -
| 타입 | 설명 | dur | 기본 볼륨 |
|---|---|---|---|
| stinger | 짧은 음악 신호, 보컬 위에 겹침 | 기본 6초 | 0.12 |
| bed / post | 보컬 아래에 음악을 깔고, 보컬이 나오면 자동으로 볼륨 낮춤 | 필수 | 0.12 |
| break | 이 지점에서 보컬을 자르고 순수 음악을 삽입, 음악이 끝나면 다시 보컬로 전환; 최종 길이가 dur초 만큼 길어짐 | 필수 | 0.5 |
| ending | 엔딩 음악: 말할 때는 볼륨 낮춤, 말이 끝나면 올리고 마지막에 페이드아웃 | '-'로 작성 | 0.22 |
src에 '-'를 쓰면 이번에 전달된 배경 음악을 사용;-@20은 배경 음악의 20초 지점부터 가져옴; 다른 음악 파일의 전체 경로도 가능하며, 마찬가지로@초를 추가 가능(추가하지 않으면 처음부터 가져옴).- stinger의 src가 '@' 없이 '-'인 경우, 기본적으로 배경 음악의 60초 지점에서 가져옵니다; 다른 타입에서 '@' 없으면 처음부터 가져옵니다.
- 작성 후 먼저 사전 검사하고, ❌가 있으면 먼저 수정:
python3 <스킬 디렉토리>/scripts/cues_lint.py <이름>_cues.tsv --master <컷된 마스터.wav> --bg <배경 음악>
이 명령은 형식, 경계 초과, 소재 존재 여부, 창 중첩, 음악 총 비율(15% 이하 권장)을 검사하고 '마스터 → 최종 파일' 타임스탬프 변환표를 제공하여 프로그램 타임라인 작성 시 직접 사용할 수 있습니다.
6단계: 2차 최종 파일 생성
MASTER_WAV=<컷된 마스터.wav> bash <스킬 디렉토리>/scripts/edit_podcast.sh - [배경 음악] <출력 디렉토리>/final <이름>
- 첫 번째 매개변수는 '-'를 사용합니다.
MASTER_WAV가 있으면 추출, 노이즈 제거 및 노멀라이제이션을 건너뛰고, 바로 CUE를 적용하고, 음악을 믹싱하고, 내보냅니다. - 배경 음악이 있고 오디오가 80초보다 긴 경우, 자동으로 오프닝/엔딩 음악을 추가합니다: 오프닝 음악이 3초 먼저 재생된 후 보컬이 들어오고, 보컬이 끝난 후 음악이 약 7초 남아 페이드아웃되며, 보컬이 나올 때 음악이 자동으로 볼륨이 낮아집니다.
- 컷이나 배경 음악이 없는 경우 1단계의 최종 파일이 최종 결과물이므로 이 단계를 실행할 필요가 없습니다.
7단계: 검수
스크립트는 마지막에 이미 한 번 재측정합니다. 독립적으로 다시 측정하려면(--check에 표시된 ffmpeg 경로 사용):
"<ffmpeg 경로>" -hide_banner -nostats -i <최종 파일.mp3> -filter_complex ebur128=peak=true -f null - 2>&1 | tail -12
- 기준 충족: Integrated가 -16 LUFS ±1 이내, True peak가 -1.5 dBFS(즉 dBTP) 초과 불가.
- 사용자에게 헤드폰으로 들어보도록 요청: 음악이 보컬을 묻지 않음; 일시 정지 시 음악이 갑자기 돌아오지 않음; 각 컷 지점이 자연스러움; 끝이 잘리지 않음; 의도적으로 남긴 일시 정지가 그대로 있음.
- 전달 시 사용자에게 알림: 최종 파일 위치, 노이즈 제거 방식, 라우드니스 및 실제 피크의 측정값, 자른 위치. 기준 미달 시 그대로 말하고 통과했다고 말하지 마십시오.
4. 편집 원칙
- 사고만 자르고, 호흡은 자르지 않음: 말실수, 대본 확인, 재녹음 폐기 구간은 자르고; 호흡, 생각할 때의 '음', 문장 사이의 망설임, 의도적으로 남긴 침묵은 자르지 않음. 확실하지 않으면 남겨둠.
- 0.8초 이내의 일시 정지는 건드리지 않음; 0.8–1.5초의 문장 사이 일시 정지는 기본적으로 모두 유지; 더 긴 일시 정지는 끊김이나 집중력 상실의 증거가 있을 때만 줄임.
- 컷 지점은 자연스러운 호흡 지점을 선택하고, 자음의 머리나 꼬리를 자르지 않음; 호흡은 통째로 유지하거나 통째로 제거.
- 배경 음악은 보컬보다 작게, 보컬이 있는 곡이나 중간 주파수(1–3kHz)가 풍부한 곡은 피함; 멜로디가 있는 음악은 단락 사이(break)에 우선 배치하고 보컬 아래에 깔지 않음; 음악의 총 길이는 일반적으로 최종 파일의 15%를 초과하지 않음.
- 자세한 내용은
references/editing-style-guide.md(스타일 및 포장),references/editing-essentials.md(편집 실행 요점 및 검수), 용어는references/audio-glossary.md를 참조.
5. 환경 변수 (모두 설정하지 않아도 됨)
| 변수 | 기본값 | 설명 |
|---|---|---|
FFMPEG | 자동 검색 | ffmpeg 경로 지정 |
DENOISE | rnnoise | rnnoise / afftdn / off |
RNNOISE_MODEL | ~/.cache/ethan-podcast-post/bd.rnnn | 노이즈 제거 모델 위치; 이미 모델이 있으면 직접 지정 가능 |
RNNOISE_URL | GitHub의 공개 모델 | 네트워크가 안 될 경우 미러 주소로 변경 가능(다운로드 후 SHA-256 검증 유지) |
INPUT_GAIN | 0 | 처리 전 녹음 전체를 높이거나 낮추는 dB; 녹음이 너무 작으면 오류 메시지의 권장 값으로 설정 |
TRIM_THRESHOLD | -50 | 앞뒤 무음 제거 임계값(dB); 시작이나 끝의 작은 소리가 잘리면 낮춤(예: -60) |
INTERVIEW_MODE | 0 | 1 = 듀얼 또는 멀티 트랙 대화 |
KEEP_MASTER | 0 | 1 = 보컬 마스터 voice_master.wav 유지 |
MASTER_WAV | 빈 값 | 2차 처리에 사용할 보컬 마스터 |
MUSIC_VOLUME | 0.12 | 배경 음악 볼륨 비율 |
INTRO_DURATION | 40 | 오프닝/엔딩 음악 창 시간(초); 오디오가 이 값의 2배보다 짧으면 추가하지 않음 |
PREROLL / TAIL | 3 / 7 | 오프닝 음악 선행 시간(초) / 보컬 종료 후 음악 유지 시간(초) |
OUTRO_SEEK | 빈 값 | 엔딩 음악을 곡의 몇 초 지점에서 가져올지 |
MP3_BITRATE | 160k | 최종 파일 비트레이트 |
OVERWRITE | 0 | 1 = 기존 최종 파일 덮어쓰기 허용(먼저 사용자 동의 필요) |
6. 자주 묻는 질문
- ffmpeg를 찾을 수 없음: 사용자에게 먼저 설치 요청(macOS
brew install ffmpeg, Linux는 패키지 관리자 사용) 또는FFMPEG=/전체경로/ffmpeg로 지정. - ffmpeg 버전: 스크립트는 버전 번호를 읽습니다. 250Hz 동적 EQ 단계의 매개변수 의미는 ffmpeg 6과 8 사이에서 변경되었으며, 두 방식 모두 사인파 신호로 보정되어 효과가 동일합니다; 검증되지 않은 버전(예: 7.x)을 만나면 이 단계를 건너뛰고 알리며, 나머지 처리는 정상 진행. 앞뒤 무음 제거 매개변수도 새 버전 동작에 맞게 조정되어 6.x와 8.x에서 동일하게 자릅니다.
- 오류: '앞뒤 무음 제거 후 거의 아무것도 남지 않음' 또는 '처리 후 라우드니스 측정 불가': 녹음이 너무 작거나 빈 경우. 말하는 내용이 확인되면 오류 메시지의
INPUT_GAIN권장 값을 사용하여 다시 실행(-70 LUFS 미만의 라우드니스는 측정 불가, 트리밍 임계값만 조정하는 것으로는 부족함); 녹음에 실제로 소리가 없으면 계속 오류가 발생하고 빈 최종 파일을 출력하지 않음. - 노이즈 제거가 afftdn을 사용: 모델 다운로드에 실패한 경우.
RNNOISE_URL을 설정하여 미러로 변경하거나RNNOISE_MODEL로 기존 모델 파일을 지정한 후 다시 실행(기존 최종 파일 덮어쓰기 전에 사용자에게 먼저 물어봄). - 끝부분 볼륨이 -80dB 미만: 녹음이 중간에 끊겼을 가능성이 높으므로 먼저 사용자와 확인하고 편집 문제로 처리하지 마십시오.
- 재녹음 구간을 하나도 찾지 못함: 자막이
voice_master.wav에서 전사되었는지 확인하고, 전사에서 명령이 어떻게 기록되었는지 확인하고--phrase로 해당 표기를 추가. - 각 단계의 명령을 보고 싶음:
bash -x <스킬 디렉토리>/scripts/edit_podcast.sh .... - zsh에서 ffmpeg 필터 매개변수를 직접 구성할 때: 변수 뒤에 콜론이 바로 오는 경우
${VAR}:...로 작성해야 합니다. 그렇지 않으면 zsh가$VAR:a,$VAR:r을 수정자로 간주하여 매개변수가 조용히 변경됩니다.
라이선스: 코드 MIT, 문서 CC BY 4.0.