$npx -y skills add jamditis/claude-skills-journalism --skill interview-transcriptionTranscription workflows, recording management, and quote extraction for journalists. Use when processing audio/video recordings, generating transcripts with timestamps, extracting quotes for fact-checking, or building source-and-recording databases. For interview question design
| 1 | # Interview transcription and management |
| 2 | |
| 3 | Practical workflows for journalists managing interviews from preparation through publication. |
| 4 | |
| 5 | ## When to activate |
| 6 | |
| 7 | - Preparing questions for an interview |
| 8 | - Processing audio/video recordings |
| 9 | - Creating or managing transcripts |
| 10 | - Organizing notes from multiple sources |
| 11 | - Building a source relationship database |
| 12 | - Generating timestamped quotes for fact-checking |
| 13 | - Converting recordings to publishable quotes |
| 14 | |
| 15 | ## Recording setup for transcription |
| 16 | |
| 17 | For pre-interview research, question design, attribution agreements, and consent scripts, use the **interview-prep** skill. The notes here cover only the recording configuration that affects transcription quality. |
| 18 | |
| 19 | ```python |
| 20 | # Standard recording configuration for clean transcription |
| 21 | RECORDING_SETTINGS = { |
| 22 | 'format': 'wav', # Lossless for transcription |
| 23 | 'sample_rate': 16000, # Whisper resamples to 16k anyway; 16k saves disk |
| 24 | 'channels': 1, # Mono is fine for speech; stereo only if mics are positionally distinct |
| 25 | 'backup': True, # Always run a backup recorder |
| 26 | } |
| 27 | |
| 28 | # File naming convention |
| 29 | # YYYY-MM-DD_source-lastname_topic.wav |
| 30 | # Example: 2026-05-08_smith_budget-hearing.wav |
| 31 | ``` |
| 32 | |
| 33 | **Two-device rule.** Always record on two devices. Phone as backup minimum. If using a wireless lav mic, the recorder built into the lav unit is one device; the phone running a backup app is the second. |
| 34 | |
| 35 | **Mono is preferred** unless each speaker has their own dedicated microphone routed to a distinct channel. Stereo with both speakers bleeding into both channels is worse for diarization than clean mono. |
| 36 | |
| 37 | ## Transcription workflows |
| 38 | |
| 39 | ### Automated transcription pipeline |
| 40 | |
| 41 | Vanilla OpenAI Whisper transcribes audio to text but does **not** assign speaker labels. To get diarized output ("Speaker 1:" / "Speaker 2:" / etc.) you need a tool that combines Whisper with a diarization model — typically **WhisperX** (`m-bain/whisperX`), which wraps faster-whisper transcription with pyannote.audio diarization and produces word-level timestamps with speaker IDs in one pass. |
| 42 | |
| 43 | ```python |
| 44 | from pathlib import Path |
| 45 | import subprocess |
| 46 | import json |
| 47 | |
| 48 | def transcribe_interview( |
| 49 | audio_path: str, |
| 50 | output_dir: str = "./transcripts", |
| 51 | diarize: bool = True, |
| 52 | hf_token: str | None = None, |
| 53 | min_speakers: int = 2, |
| 54 | max_speakers: int = 2, |
| 55 | ) -> dict: |
| 56 | """ |
| 57 | Transcribe an interview using WhisperX (Whisper + pyannote diarization). |
| 58 | Returns a transcript with word-level timestamps and speaker labels. |
| 59 | |
| 60 | Diarization needs a Hugging Face token with access to the pyannote |
| 61 | speaker-diarization-3.1 model. Accept the model EULA at |
| 62 | huggingface.co/pyannote/speaker-diarization-3.1 once, then pass the token. |
| 63 | """ |
| 64 | Path(output_dir).mkdir(exist_ok=True) |
| 65 | |
| 66 | cmd = [ |
| 67 | 'whisperx', audio_path, |
| 68 | '--model', 'large-v3', |
| 69 | '--output_format', 'json', |
| 70 | '--output_dir', output_dir, |
| 71 | '--language', 'en', |
| 72 | '--compute_type', 'int8', # CPU-friendly; use 'float16' on GPU |
| 73 | '--min_speakers', str(min_speakers), |
| 74 | '--max_speakers', str(max_speakers), |
| 75 | ] |
| 76 | |
| 77 | if diarize: |
| 78 | cmd.append('--diarize') |
| 79 | if hf_token: |
| 80 | cmd += ['--hf_token', hf_token] |
| 81 | |
| 82 | subprocess.run(cmd, check=True, capture_output=True) |
| 83 | |
| 84 | json_path = Path(output_dir) / f"{Path(audio_path).stem}.json" |
| 85 | with open(json_path) as f: |
| 86 | return json.load(f) |
| 87 | |
| 88 | def format_for_editing(transcript: dict) -> str: |
| 89 | """Convert to journalist-friendly format with timestamps.""" |
| 90 | lines = [] |
| 91 | for segment in transcript.get('segments', []): |
| 92 | timestamp = format_timestamp(segment['start']) |
| 93 | text = segment['text'].strip() |
| 94 | lines.append(f"[{timestamp}] {text}") |
| 95 | return '\n\n'.join(lines) |
| 96 | |
| 97 | def format_timestamp(seconds: float) -> str: |
| 98 | """Convert seconds to HH:MM:SS format.""" |
| 99 | h = int(seconds // 3600) |
| 100 | m = int((seconds % 3600) // 60) |
| 101 | s = int(seconds % 60) |
| 102 | return f"{h:02d}:{m:02d}:{s:02d}" |
| 103 | ``` |
| 104 | |
| 105 | **Falling back to plain Whisper.** If diarization is overkill or you can't get a Hugging Face token, drop the `--diarize` flag — the model still produces accurate timestamped transcription and you label speakers manually based on context. `faster-whisper` (CTranslate2 backend) is the speed-optimized variant and works the same way at the CLI. `whisper.cpp` is the C++ port for resource-constrained machines (Raspberry Pi, older laptops); it doesn't include diarization but runs the small/medium models on CPU comfortably. |
| 106 | |
| 107 | ### Manual transcription template |
| 108 | |
| 109 | For sensitive interviews or when AI transc |