인용문: 소스 → 트랜스크립트 → 인용문 → 큐 및 클립
도구: agent-citations/citekit.py (stdlib Python + mlx_whisper 및 ffmpeg). 입력 파일을 절대 덮어쓰지 않으며, --force 없이는 자체 출력 파일을 교체하지 않습니다.
배치 레이아웃
active_edits/<subject>/ 아래 주제별 폴더:
sources/video_sources.txt 사용자의 URL 목록
sources/audio/<id>.m4a .info.json yt-dlp에서 다운로드 (1단계)
sources/transcripts/<id>.json .md 2단계
podcast/<ep>.json .md 자체 에피소드 캡션, 가져오기 (2b단계)
sources/quotes.json 3단계, 사용자가 작성
sources/citations/ 4단계 출력 파일
1. 다운로드 (사용자가 직접 실행)
여기서 YouTube는 익명 yt-dlp를 차단합니다. 사용자에게 실행 명령어를 직접 제공하지 말고 아래 명령어를 알려주세요. Firefox 쿠키와 tv/safari 클라이언트가 필요하며, 오디오 크기를 작게 유지합니다 (Whisper는 16kHz 모노만 필요):
cd active_edits/<subject>/sources && for id in ID1 ID2; do yt-dlp --cookies-from-browser firefox --extractor-args "youtube:player_client=tv,web_safari,mweb" -f "wa/w" -x --audio-format m4a --postprocessor-args "ExtractAudio:-ac 1 -ar 16000" --write-info-json --write-auto-subs --sub-langs en --sub-format json3 --no-overwrites -o "audio/%(id)s.%(ext)s" "https://youtu.be/$id"; done
채널의 전체 영상을 처리하려면 먼저 목록을 확인한 후 선택하세요: yt-dlp --flat-playlist --print "%(id)s %(duration_string)s %(title)s" https://www.youtube.com/@CHANNEL/videos.
목록이 길면 Jev (vibe-classification)로 제목을 분류하세요.
2. 트랜스크립션
citekit.py transcribe sources/audio/<id>.m4a: mlx-whisper large-v3-turbo 모델, 단어 단위 타임스탬프. 초기 프롬프트는 동영상 제목과 설명이며, 대부분의 이름 철자를 수정합니다. 이 Mac에서 실시간의 약 85배 속도로 실행됨 (15분 분량에 약 35초 소요; 모델은 한 번 다운로드).citekit.py captions sources/audio/<id>.en.json3: YouTube 자동 캡션. 즉시 처리되지만 구두점이 없음. tv 클라이언트는 종종 자동 캡션을 저장하지 않습니다.- 2b. 자체 에피소드:
citekit.py itt EP.itt --id podcastN --media EP.mp4 --out active_edits/<subject>/podcast --fix 'REGEX=>Right'. FCP(Final Cut Pro) 캡션은 이름을 망가뜨립니다. 에피소드 1의 경우:--fix '\bver ?m+[iu]l+e?\b=>Vermeule' --fix '\bvermil\b=>Vermeule' --fix '\bAsian Vermeule=>Adrian Vermeule'. 에피소드 1에는podcast/podcast1.fixes.json에 기록된 단어 수준 수정 사항이 있으며 (Schmidt→Schmitt 등), 재가져오기 시 재적용됩니다.
발견 즉시 오인식 수정 (질문하지 말고)
확실한 오청음(이름, 잘못된 인용문, Whisper가 두 번 인식한 말 더듬기 등)을 발견하면 수정하세요:
citekit.py fix TRANSCRIPT 'old words=>new words' [...] [--near SECONDS]. 이 명령어는 <id>.json/.md 파일을 직접 수정하며 (새 단어는 기존 구간의 타이밍을 공유하고, 일치는 세그먼트 경계를 넘을 수 있으며, 후행 구두점은 유지됨), 수정되지 않은 원본 출력을 <id>.raw.json으로 보관하고 모든 수정 사항을 <id>.fixes.json에 기록합니다. transcribe와 itt는 이 로그를 재생하므로 재실행 시 수정 사항이 유지됩니다. 확실하지 않은 경우? 추측 대신 해당 지점을 다시 들어보세요: ffmpeg -ss T -to T2 -i audio.m4a x.wav && mlx_whisper x.wav --output-format txt. 먼저 수정한 후, 수정된 단어를 인용하세요: locate는 트랜스크립트에서 일치를 찾습니다.
3. 인용문 발굴 (사용자 판단)
- 에피소드 트랜스크립트가 있다면 읽고, 호스트들이 주제에 대해 언급한 주장을 각각 에피소드 타임코드와 함께 나열하세요. 이 순간들이 클립으로 뒷받침하거나 반박할 수 있는 지점입니다.
- 각 주장에 대해
citekit.py search 'regex' sources/transcripts/명령어로 후보를 찾은 후, 일치 지점 주변의.md파일을 읽으세요. 전체 강의도 읽으세요. 가장 좋은 인용문이 호스트들이 언급한 지점과 항상 일치하는 것은 아닙니다. - 좋은 인용문은 짧고(5–25초 분량), 자체적으로 완결되며, 화자 자신의 입장을 명확히 나타내고(상대방 의견 요약이 아닌), 하단 자막(로우어 서드)에 인용하기 적합해야 합니다. 트랜스크립트의 정확한 단어를 유지하세요. 정리하지 마세요. 그렇지 않으면
locate가 찾을 수 없습니다. sources/quotes.json작성:
{"project": "Ep1 Vermeule citations", "fps": 29.97, "episode": "../podcast/podcast1.json",
"quotes": [{"id": "publius-weak-exec", "source": "Bw9M01S8MFI", "approx": 1234,
"text": "트랜스크립트의 정확한 단어",
"episode_at": 2575, "episode_context": "호스트들이 해밀턴과 로마 독재관에 대해 논의",
"why": "그의 주제 진술 자체", "tags": ["executive", "Federalist"],
"citation": {"author": "~Adrian Vermeule", "title": "The Publius Paradox", "date": "5 Jun, 2018",
"publication": "Chorley Lecture, LSE"}}]}
episode (quotes.json 기준 상대 경로)는 우리 에피소드의 트랜스크립트입니다. 이와 함께 각 인용문의 episode_at (호스트들이 해당 지점을 언급하는 대략적 시간)은 삽입 지점이 됩니다: 그 시점에 재생 중인 호스트 문장의 끝 부분입니다. 전체 생각을 인용하세요. 다음 문장이 이미지나 주장을 완성한다면 포함하세요 (5–25초 클립이 목표이지 엄격한 제한이 아닙니다).
citation의 기본값은 동영상 제목, 업로드 날짜 및 채널입니다. 실제 강연 장소와 날짜(설명에서)는 최상위 레벨 "sources": {"<id>": {"citation": {...}}}에 한 번만 입력하세요. 인용문 자체의 citation은 이를 재정의합니다. 저자 이름에는 쇼의 ~ 접두사를 붙이고, \n은 긴 제목을 두 줄로 나눕니다.
그가 인용하는 내용(해밀턴, 카프카, 법학대전 등)은 원문과 대조 확인하세요. Whisper는 그럴듯한 잘못된 단어를 듣습니다: Publius 강의에서 Kafka의 "sacrificial pitchers"를 "pictures"로 인식했습니다.
4. 큐와 클립
citekit.py cues sources/quotes.json은 각 인용문을 단어 타임스탬프에 스냅하고(퍼지 매칭; 일치 점수가 < 0.8이면 경고) sources/citations/에 다음 파일을 작성합니다:
| 파일 | 용도 |
|---|---|
citations.md | 에피소드 순서대로 검토 시트: 삽입 시간 (기록 시간 및 이전 삽입 후), 컷 직전 호스트의 말, 인용문, 소스/클립 시간, YouTube ?t= 링크 |
clips.json | 에이전트 편집용: 소스 in/out, 패딩 in/out (기본 각 면 5초, --pad-before/--pad-after, 인용문별 pad_before/pad_after), insert_at, insert_at_after_earlier_inserts, insert_after |
citation_cues.json | agent-motion/titles_fcpxml.py용: 인용 v2 로우어 서드가 클립이 삽입된 에피소드의 타임라인에 맞춰, 화자가 말을 시작할 때 표시되도록 타이밍 조정. 배치되지 않은 인용문은 연속적으로 배치 |
download_clips.sh | 사용자가 실행: 해당 패딩 범위만 고화질 비디오 다운로드 (--download-sections) |
그런 다음 citekit.py audio-clips sources/citations/clips.json은 로컬 오디오에서 패딩된 오디오 미리보기를 잘라내어 귀로 확인할 수 있게 합니다. FCP에서 로우어 서드를 배치하려면 motion-graphics 기술을 따르세요: python3 agent-motion/titles_fcpxml.py sources/citations/citation_cues.json -o citations.fcpxml.
참고 사항
-
transcribe는 자체적으로 16kHz WAV로 디코딩하며, 오디오가 비디오보다 훨씬 짧게 디코딩되면 거부합니다. 다른-f옵션으로 재개된 yt-dlp 다운로드는 두 코덱을 하나의 .m4a에 결합하며 (2026-10-18 확인: AAC-LC 다음 HE-AAC), ffmpeg는 전환 이후의 모든 것을 자동으로 삭제합니다.--no-continue로 다시 다운로드하세요. 포맷 변경 시 절대 재개하지 마세요. -
클립을 귀로 확인하거나 저렴하게 확인하세요:
mlx_whisper citations/audio_clips/<id>.m4a --output-format txt를 실행하면 인용문과 몇 초의 리드인 및 테일이 표시됩니다. -
Whisper 타임스탬프는 단어 수준에서 약 ±0.3초 정확도입니다. 그래서 클립 패딩이 존재합니다. FCP iTT 캡션은 전체 줄만 시간을 지정하므로,
itt는 단어를 각 줄에 균등하게 분산합니다. -
Wispr Flow 커넥터는 Meeting Recorder 트랜스크립트만 읽습니다. 파일을 트랜스크립션할 수 없습니다.