Community编程与开发github.com

ethan-podcast-post

处理用户自己录的播客或口播录音(本机音频文件):降噪、修剪首尾静音、人声均衡与压缩、线性响度标准化到约 -16 LUFS 并导出 MP3;可选片头片尾配乐和分段配乐(cue 表,出片前先预检);给出长停顿收紧和重录口令标记段的剪辑建议,按用户确认的剪切表剪切。用户说「剪播客」「处理录音」「播客降噪」「响度标准化」「母带处理」「做 cue 表」「剪掉重录的地方」时使用。需要本机装有 ffmpeg 和 Python 3.9 以上,仅支持 macOS 和 Linux。不生成或克隆人声、不配音、不转写、不发布。

ethan-podcast-post 是什么?

ethan-podcast-post is a Claude Code agent skill that 处理用户自己录的播客或口播录音(本机音频文件):降噪、修剪首尾静音、人声均衡与压缩、线性响度标准化到约 -16 LUFS 并导出 MP3;可选片头片尾配乐和分段配乐(cue 表,出片前先预检);给出长停顿收紧和重录口令标记段的剪辑建议,按用户确认的剪切表剪切。用户说「剪播客」「处理录音」「播客降噪」「响度标准化」「母带处理」「做 cue 表」「剪掉重录的地方」时使用。需要本机装有 ffmpeg 和 Python 3.9 以上,仅支持 macOS 和 Linux。不生成或克隆人声、不配音、不转写、不发布。.

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Ethanyang9512/ai-creator-studio/tree/main/skills/ethan-podcast-post

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Podcast Editing & Mastering

Turns raw podcast or voice-over recordings into a publishable final product: noise reduction, trim leading/trailing silence, vocal EQ and compression, loudness normalization to approximately -16 LUFS, export to MP3. Optionally add intro/outro music and segment-based music, and provide editing suggestions (which long pauses can be tightened, where retakes are) for user confirmation before cutting.

The scripts/ and references/ directories referred to below are located within this skill's directory. At runtime, use the full path, for example: bash <skill-directory>/scripts/edit_podcast.sh.

1. Clarify with the User Before Starting

  • ffmpeg must be installed locally (free, open-source audio tool). If not present, ask the user to install it: macOS: brew install ffmpeg, Linux: use the system package manager (e.g., apt install ffmpeg). The script searches in this order: environment variable FFMPEG → ffmpeg in system PATH → common installation paths /opt/homebrew/bin, /usr/local/bin. Tested with versions 6.0 and 8.1.
  • Requires bash and Python 3.9+, using only the standard library (no additional Python packages needed).
  • Only supports macOS and Linux: Tested on macOS; Linux should work; Windows requires WSL (not tested).
  • First-time noise reduction will download a public noise reduction model (~300KB) from the internet (RNNoise), stored in ~/.cache/ethan-podcast-post/, and verified via SHA-256. If download fails or checksum mismatch, automatically falls back to ffmpeg's built-in afftdn noise reduction. The script will print which method it used; you must inform the user. Model attribution is in references/third-party.md.
  • All processing is local: No audio is uploaded, no charges apply.
  • What we don't do: We do not generate or clone voices, do not dub, do not transcribe (if subtitles are needed, ask the user to provide an SRT file), do not publish to any platform, and do not provide music (background music must be supplied by the user, who must confirm they have the rights to use it).

2. File Safety (Mandatory)

  1. Original recording is read-only: The script only reads it; it does not modify, move, or delete it.
  2. All output goes to a new directory: Creates a new directory next to the recording (e.g., Episode3_post/). Do not place output in the same directory as the recording or background music. The script will stop if it detects the output directory is the same as the input file's directory.
  3. No overwriting without permission: The script stops if the output directory already contains a final file, audio_raw.wav, or voice_master.wav. To overwrite, first tell the user which files will be overwritten and that the old files cannot be recovered. Only proceed after the user agrees and sets OVERWRITE=1 before re-running. The same rule applies to editing suggestions, cutting, and vocal processing scripts: they stop if identically named output exists; proceed only after the user agrees to use --overwrite.
  4. Confirm before deletion: Any deletion (including cleanup of large files like audio_raw.wav) must be communicated to the user—specify which file and why—and get explicit consent before proceeding. Intermediate files are placed in a temporary subdirectory within the output directory, created for that run only; at the end, only this subdirectory is cleaned.
  5. What to cut is determined by the user: The editing suggestions are just a candidate list; nothing is cut until the user confirms.

3. Processing Workflow

First, run an environment check and inform the user of the results:

bash <skill-directory>/scripts/edit_podcast.sh --check

This will list the found ffmpeg, its version, whether 250Hz dynamic EQ is enabled (see section 6), and whether the noise reduction model is ready. If ffmpeg is not found, it will provide installation instructions; install it before proceeding.

Step 1: First Pass – Produce Vocal Master and Preview Version

KEEP_MASTER=1 bash <skill-directory>/scripts/edit_podcast.sh <recording-file> <output-directory>/pass1 <name>
  • The recording can be wav, m4a, mp3, mp4, or any format ffmpeg can read. For dual or multi-speaker conversations, add INTERVIEW_MODE=1 (stronger compression, and automatically levels out volume differences between speakers).
  • Processing order: Extract to 48kHz WAV → Quality check → Trim leading/trailing silence (internal pauses are left untouched) → De-click → 80Hz high-pass → Noise reduction → 250Hz dynamic EQ → De-esser → Compression → 5kHz brightness boost → Linear gain to -16 LUFS → Limiter → Export MP3 → Re-measure loudness.
  • Outputs:
    • <name>_final.mp3: The final file without music (also serves as the preview version).
    • voice_master.wav: The vocal master (with leading/trailing silence trimmed); used for subsequent transcription, editing suggestions, and cutting.
    • audio_raw.wav: A WAV copy of the original recording (written to the output directory only after all processing succeeds); useful for re-processing later, but can be large.
  • Review the following items in the output and inform the user:
    • If tail volume is below -80dB: The recording may have been interrupted mid-way; confirm this with the user first.
    • Noise reduction method used: RNNoise or afftdn.
    • Final loudness should be -16 LUFS ±1, true peak not higher than -1.5 dBTP. If the script reports an out-of-range value, inform the user accurately.
  • If the recording is too quiet or empty, the script will error and stop, producing no empty final file. The error will include a suggested value based on the recording's peak level: first confirm with the user that the recording indeed contains speech, then re-run with INPUT_GAIN=<suggested-value> (in dB, this is added on top of the current INPUT_GAIN setting) to bring the entire segment to a normal level. When this error occurs, no files remain in the output directory, so you can re-run using the same directory. Also advise the user to increase the recording input volume for future recordings.

Step 2: Editing Suggestions (Candidates Only, No Audio Changes)

Transcription must be generated from voice_master.wav from Step 1 (or from the un-music-version MP3 from Step 1, as their timestamps are identical). Do not use transcription from the original recording: its leading/trailing silence has not been trimmed, so timestamps will not align, and the detected positions will be off.

Find long pauses that can be tightened:

python3 <skill-directory>/scripts/edit_cuts.py suggest <output-directory>/pass1/voice_master.wav \
    [--srt subtitles.srt] [--protect 15:00.0-15:03.0 ...]
  • By default, pauses longer than 1.5 seconds are candidates, suggested to be tightened to 1.2 seconds (keeping half the room noise on each side of the pause).
  • Pauses deliberately left by the user (e.g., silence after an important statement) can be marked with --protect start-end, with 0.5 second buffer on each side, and will not be suggested for cutting.
  • If obvious long pauses are not found, the noise floor may be too high; try lowering --noise from the default -45 to -40 and re-run.
  • If a 40-minute segment has more than 15 candidates or a cumulative candidate length exceeding 60 seconds, the script will warn that the parameters are too aggressive: increase --min-pause, and do not cut all of them.

Find retake segments marked by a retake phrase (requires subtitles):

python3 <skill-directory>/scripts/edit_cuts.py retakes <output-directory>/pass1/voice_master.wav --srt subtitles.srt [--phrase your-phrase]
  • The convention during recording: mistake → pause >1 second → calmly say a retake phrase → pause >1 second → re-speak from the beginning of the sentence.
  • Default phrase is "重来重来" (Chinese for "start over"). Use --phrase to set your own phrase. If the transcription software often writes the phrase as homophones, use multiple --phrase flags to include all variant spellings. Choose a phrase that would not appear naturally in the content; saying it twice in a row reduces false positives.
  • The script finds the retake phrase in the subtitles, matches the re-spoken sentence's beginning back to the incorrectly spoken sentence, and outputs cut intervals with confidence levels (high / mid / low). Low-confidence suggestions must be auditioned by the user before cutting.

Both commands output voice_master_suggested_cuts.tsv and voice_master_retake_cuts.tsv respectively (will not overwrite existing files; user must agree to use --overwrite). Present the candidates to the user one by one (time, context, time saved). After the user confirms which cuts to apply, copy those rows into a new cuts.tsv file (tab-separated: start, end, notes; times in MM:SS.s format).

Step 3: Apply Cuts

python3 <skill-directory>/scripts/edit_cuts.py apply <output-directory>/pass1/voice_master.wav --cuts cuts.tsv [--srt subtitles.srt]
  • Cuts are sample-accurate, with 15ms equal-power crossfade at seams. Outputs a 24-bit voice_master_edited.wav and an edit map voice_master_edited.edit_map.json (recording source and position for each retained segment).
  • When --srt is provided, intact subtitle entries are shifted accordingly. If a subtitle is cut in half, the script refuses to apply—remove --srt and re-run, then ask the user to re-transcribe the edited master.
  • After cutting, ask the user to listen to a few seconds around each cut seam: no clicks, no double inhalation, no tonal discontinuity.

Step 4 (Optional): Apply Vocal Effects to Specific Segments (e.g., quotes, memories)

python3 <skill-directory>/scripts/voice_fx.py <edited-master.wav> --fx fx.tsv
python3 <skill-directory>/scripts/voice_fx.py --list    # View the five presets
  • Presets: quote (reading letters, others' text), archive (old newspapers, documents), telephone (relayed phone conversation), memory (recollection, dream), radio (old broadcast, max once per episode).
  • fx.tsv format: tab-separated columns: preset, start, end, fade-in, fade-out, notes. Use - for default fade durations.
  • Processing preserves sample-accurate length and loudness; outputs <original-name>_fx.wav.

Step 5 (Optional): Music and Cue Sheet

Only do this if the user provides background music. First write the cue sheet, then lint it. The cue sheet must be placed in the second-pass output directory, with filename <name>_cues.tsv.

Cue sheet format: tab-separated, 7 columns. Lines starting with # are ignored. Write - to use default value. time is in MM:SS.s relative to the vocal master timeline. Times below are examples:

# 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	-	-	-	-
TypePurposedurDefault Volume
stingerShort musical accent, overlaid on voiceDefault 6 s0.12
bed / postMusic bed under voice; auto-duck when voice appearsRequired0.12
breakCuts voice, inserts pure music segment, resumes voice after music ends; total episode length increases by dur secondsRequired0.5
endingOutro music: ducks under speech, fades up after speech ends, then fades outWrite -0.22
  • src: Write - to use the currently passed background music file; -@20 means start from the 20th second of the background music. You can also write the full path to another music file, optionally with @seconds (without @, start from the beginning).
  • For stinger, writing - without @ defaults to starting from the 60th second of the background music; for other types without @, start from the beginning.
  • After writing the cue sheet, lint it first. Fix any ❌ errors before proceeding:
python3 <skill-directory>/scripts/cues_lint.py <name>_cues.tsv --master <edited-master.wav> --bg <background-music>

This checks format, boundaries, media existence, window overlaps, music-to-speech ratio (recommended ≤15%), and provides a "master → final" timestamp conversion table for writing the program timeline.

Step 6: Second Pass – Produce Final File

MASTER_WAV=<edited-master.wav> bash <skill-directory>/scripts/edit_podcast.sh - <background-music> <output-directory>/final <name>
  • First argument is -. When MASTER_WAV is set, the script skips extraction, noise reduction, and normalization; it directly overlays cue sheet music, mixes, and exports.
  • If background music is provided and the audio is longer than 80 seconds, intro/outro music is automatically added: intro music plays for 3 seconds before voice enters; after voice ends, music continues for about 7 seconds then fades out; music automatically ducks when voice is present.
  • If no cutting and no music is needed, the Step 1 final file is the final product; skip this step.

Step 7: Verification

The script already does a final loudness measurement. To independently verify (using the ffmpeg path shown by --check):

"<ffmpeg-path>" -hide_banner -nostats -i <final.mp3> -filter_complex ebur128=peak=true -f null - 2>&1 | tail -12
  • Pass: Integrated loudness within -16 LUFS ±1, True peak no higher than -1.5 dBFS (dBTP).
  • Ask the user to listen with headphones: music should not overpower the voice; music should not jump back abruptly during pauses; each cut seam should sound natural; the ending should not be cut off; deliberately left pauses should remain.
  • When delivering, tell the user: final file location, noise reduction method, measured loudness and true peak values, and which cuts were applied. If values are out of spec, report honestly; do not claim they passed.

4. Editing Principles

  • Cut only mistakes, not breaths: Cut stumbles, reading distractions, retake waste segments. Do not cut breaths, thinking sounds like "um", inter-sentence hesitations, or deliberately left silence. When in doubt, leave it in.
  • Pauses under 0.8 seconds remain untouched; 0.8–1.5 second inter-sentence pauses are kept by default; longer pauses require evidence of stumbling or distraction to tighten.
  • Place cut points at natural breath boundaries; do not truncate the beginning or end of words or trailing sounds. Breaths are either fully kept or fully removed.
  • Background music should be quieter than the voice; avoid songs with prominent vocals or heavy midrange (1–3kHz). Prefer placing melodic music between segments (break) rather than under continuous speech. Total music duration should generally not exceed 15% of the final file.
  • Detailed guidelines are in references/editing-style-guide.md (style and packaging), references/editing-essentials.md (execution and verification), and references/audio-glossary.md (terminology).

5. Environment Variables (All Optional)

VariableDefaultPurpose
FFMPEGAuto-detectSpecify ffmpeg path
DENOISErnnoisernnoise / afftdn / off
RNNOISE_MODEL~/.cache/ethan-podcast-post/bd.rnnnPath to noise reduction model; can point to an existing file
RNNOISE_URLPublic GitHub modelAlternative mirror URL if internet is restricted (SHA-256 still checked after download)
INPUT_GAIN0dB to boost (or cut) the entire recording before processing; set to the value from the error message when recording is too quiet
TRIM_THRESHOLD-50dB threshold for trimming leading/trailing silence; lower if gentle sounds at start/end are being cut, e.g., -60
INTERVIEW_MODE01 = dual or multi-speaker conversation
KEEP_MASTER01 = keep vocal master voice_master.wav
MASTER_WAV(empty)Vocal master path for second pass
MUSIC_VOLUME0.12Background music volume ratio
INTRO_DURATION40Intro/outro music window in seconds; skipped if audio is shorter than 2x this value
PREROLL / TAIL3 / 7Intro music lead seconds / seconds of music after voice ends
OUTRO_SEEK(empty)Seconds into the track from which to take outro music
MP3_BITRATE160kFinal MP3 bitrate
OVERWRITE01 = allow overwriting existing output (must first obtain user consent)

6. Frequently Asked Questions

  • Cannot find ffmpeg: Ask the user to install it (macOS: brew install ffmpeg, Linux: use package manager), or specify the path with FFMPEG=/full/path/to/ffmpeg.
  • ffmpeg version: The script reads the version number. The meaning of parameters for the 250Hz dynamic EQ function changed between ffmpeg 6 and 8; both versions have been calibrated using sine wave signals to produce identical results. For unverified versions (e.g., 7.x), this stage is skipped with a notice, but all other processing continues. The trim parameters have also been adjusted for new behavior; 6.x and 8.x cut identically.
  • Error "Almost nothing left after trimming silence" or "Cannot measure loudness after processing": The recording is too quiet or empty. Confirm there is speech, then re-run with the INPUT_GAIN value suggested in the error message (loudness below -70 LUFS is unmeasurable; adjusting trim threshold alone is insufficient). If there truly is no audio, the error will persist and no empty final file will be produced.
  • Noise reduction used afftdn: The model download failed. You can set RNNOISE_URL to an alternative mirror, or use RNNOISE_MODEL to point to an existing model file, then re-run (ask the user before overwriting any existing output).
  • Tail volume below -80dB: The recording likely stopped mid-way; confirm with the user first. Do not treat this as a trimming issue.
  • No retake segments found: Verify that the transcription was generated from voice_master.wav, then check how the retake phrase was transcribed (it may have been misspelled). Use --phrase to add that spelling.
  • See all commands executed: Run with bash -x <skill-directory>/scripts/edit_podcast.sh ....
  • Building ffmpeg filter arguments in zsh: When a variable is immediately followed by a colon, use ${VAR}:...; otherwise, zsh may interpret $VAR:a or $VAR:r as modifiers, silently altering the parameter.

License: Code MIT, Documentation CC BY 4.0.

相关技能