podcast-production

Podcast Production Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "podcast-production" with this command: npx skills add mindmorass/reflex/mindmorass-reflex-podcast-production

Podcast Production Patterns

Best practices for podcast production, editing, and distribution.

Recording Setup

Audio Configuration

from dataclasses import dataclass from typing import Optional, List

@dataclass class AudioSettings: sample_rate: int = 48000 # Hz bit_depth: int = 24 channels: int = 1 # Mono per track format: str = "wav" # Lossless for recording

@dataclass class RecordingTrack: name: str input_device: str settings: AudioSettings file_path: str

class RecordingSession: def init(self, project_name: str, output_dir: str): self.project_name = project_name self.output_dir = output_dir self.tracks: List[RecordingTrack] = []

def add_track(self, name: str, input_device: str) -> RecordingTrack:
    """Add recording track for participant."""
    settings = AudioSettings()
    file_path = f"{self.output_dir}/{self.project_name}_{name}.wav"

    track = RecordingTrack(
        name=name,
        input_device=input_device,
        settings=settings,
        file_path=file_path
    )
    self.tracks.append(track)
    return track

Remote Recording

import asyncio import websockets from dataclasses import dataclass

@dataclass class RemoteParticipant: name: str connection_url: str local_recording: bool = True # Record locally for best quality

class RemoteRecordingSession: """Coordinate remote podcast recording."""

def __init__(self):
    self.participants: List[RemoteParticipant] = []
    self.sync_server = None

async def start_sync_server(self, port: int = 8765):
    """Start sync server for coordinating recording."""
    async def handler(websocket, path):
        async for message in websocket:
            if message == "SYNC":
                # Broadcast sync signal to all participants
                await asyncio.gather(*[
                    ws.send("START") for ws in self.connected_clients
                ])

    self.sync_server = await websockets.serve(handler, "0.0.0.0", port)

def generate_recording_instructions(self) -> str:
    """Generate instructions for remote participants."""
    return """

Remote Recording Setup

Equipment

  • Use a USB microphone or audio interface
  • Wear headphones to prevent echo
  • Choose a quiet room with minimal echo

Software

  • Use Audacity, GarageBand, or similar
  • Settings: 48kHz sample rate, 24-bit
  • Record in WAV format (not MP3)

Before Recording

  1. Test audio levels (peaks around -12dB)
  2. Disable notifications
  3. Close unnecessary applications

During Recording

  • Wait for sync signal before speaking
  • Clap at start for sync alignment
  • Note any interruptions

After Recording

  • Export as WAV
  • Upload to shared folder: {upload_link}
  • Name file: {episode}_YourName.wav """

Audio Editing

FFmpeg Audio Processing

Normalize audio levels

ffmpeg -i input.wav -af "loudnorm=I=-16:TP=-1.5:LRA=11" output.wav

Remove background noise (using noise profile)

ffmpeg -i input.wav -af "afftdn=nf=-25" cleaned.wav

Apply compression for consistent levels

ffmpeg -i input.wav -af "acompressor=threshold=-20dB:ratio=4:attack=5:release=50" compressed.wav

Apply podcast EQ

ffmpeg -i input.wav -af "equalizer=f=100:t=h:w=200:g=-3,equalizer=f=3000:t=h:w=1000:g=2,equalizer=f=8000:t=h:w=2000:g=1" eq.wav

High-pass filter (remove low rumble)

ffmpeg -i input.wav -af "highpass=f=80" filtered.wav

De-ess (reduce sibilance)

ffmpeg -i input.wav -af "adeclick=w=55:o=50" deessed.wav

Complete podcast processing chain

ffmpeg -i input.wav -af "
highpass=f=80,
afftdn=nf=-25,
acompressor=threshold=-20dB:ratio=4:attack=5:release=50,
equalizer=f=100:t=h:w=200:g=-3,
equalizer=f=3000:t=h:w=1000:g=2,
loudnorm=I=-16:TP=-1.5:LRA=11
" processed.wav

Python Audio Processing

import subprocess from dataclasses import dataclass from typing import List, Optional

@dataclass class AudioEdit: """Represents an audio edit operation.""" start_time: float # seconds end_time: float operation: str # cut, silence, crossfade

class PodcastEditor: def init(self, input_file: str): self.input_file = input_file self.edits: List[AudioEdit] = []

def cut(self, start: float, end: float):
    """Mark section for removal."""
    self.edits.append(AudioEdit(start, end, "cut"))

def silence(self, start: float, end: float):
    """Replace section with silence."""
    self.edits.append(AudioEdit(start, end, "silence"))

def process_edits(self, output_file: str):
    """Apply all edits and export."""
    # Sort edits by start time
    self.edits.sort(key=lambda e: e.start_time)

    # Build filter complex for cuts
    if not self.edits:
        subprocess.run([
            "ffmpeg", "-i", self.input_file,
            "-c:a", "copy", output_file
        ])
        return

    # Generate segments to keep
    segments = []
    current_time = 0

    for edit in self.edits:
        if edit.operation == "cut" and edit.start_time > current_time:
            segments.append((current_time, edit.start_time))
        current_time = edit.end_time

    # Add final segment
    segments.append((current_time, None))

    # Build FFmpeg filter
    filter_parts = []
    for i, (start, end) in enumerate(segments):
        if end:
            filter_parts.append(f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[a{i}]")
        else:
            filter_parts.append(f"[0:a]atrim=start={start},asetpts=PTS-STARTPTS[a{i}]")

    # Concatenate segments
    concat_inputs = "".join(f"[a{i}]" for i in range(len(segments)))
    filter_complex = ";".join(filter_parts) + f";{concat_inputs}concat=n={len(segments)}:v=0:a=1[out]"

    subprocess.run([
        "ffmpeg", "-i", self.input_file,
        "-filter_complex", filter_complex,
        "-map", "[out]",
        output_file
    ])

def normalize(self, output_file: str):
    """Apply loudness normalization."""
    subprocess.run([
        "ffmpeg", "-i", self.input_file,
        "-af", "loudnorm=I=-16:TP=-1.5:LRA=11",
        output_file
    ])

def add_intro_outro(
    self,
    intro_file: str,
    outro_file: str,
    output_file: str
):
    """Add intro and outro music."""
    subprocess.run([
        "ffmpeg",
        "-i", intro_file,
        "-i", self.input_file,
        "-i", outro_file,
        "-filter_complex",
        "[0:a][1:a][2:a]concat=n=3:v=0:a=1[out]",
        "-map", "[out]",
        output_file
    ])

def mix_background_music(
    self,
    music_file: str,
    output_file: str,
    music_volume: float = 0.1
):
    """Mix background music under voice."""
    subprocess.run([
        "ffmpeg",
        "-i", self.input_file,
        "-i", music_file,
        "-filter_complex",
        f"[1:a]volume={music_volume}[music];[0:a][music]amix=inputs=2:duration=first[out]",
        "-map", "[out]",
        output_file
    ])

Transcription

Whisper Transcription

import whisper from dataclasses import dataclass from typing import List, Optional

@dataclass class TranscriptSegment: start: float end: float text: str speaker: Optional[str] = None

class PodcastTranscriber: def init(self, model_size: str = "medium"): self.model = whisper.load_model(model_size)

def transcribe(
    self,
    audio_file: str,
    language: str = "en"
) -> List[TranscriptSegment]:
    """Transcribe audio file."""
    result = self.model.transcribe(
        audio_file,
        language=language,
        word_timestamps=True
    )

    segments = []
    for seg in result["segments"]:
        segments.append(TranscriptSegment(
            start=seg["start"],
            end=seg["end"],
            text=seg["text"].strip()
        ))

    return segments

def to_srt(self, segments: List[TranscriptSegment]) -> str:
    """Convert to SRT subtitle format."""
    def format_time(seconds: float) -> str:
        hours = int(seconds // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        millis = int((seconds % 1) * 1000)
        return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"

    lines = []
    for i, seg in enumerate(segments, 1):
        lines.append(str(i))
        lines.append(f"{format_time(seg.start)} --> {format_time(seg.end)}")
        lines.append(seg.text)
        lines.append("")

    return "\n".join(lines)

def to_chapters(
    self,
    segments: List[TranscriptSegment],
    min_gap: float = 5.0
) -> List[dict]:
    """Generate chapter markers from transcript."""
    chapters = []
    current_chapter_start = 0
    current_text = []

    for i, seg in enumerate(segments):
        current_text.append(seg.text)

        # Check for chapter break (long pause or topic change)
        if i < len(segments) - 1:
            gap = segments[i + 1].start - seg.end
            if gap > min_gap:
                chapters.append({
                    "start": current_chapter_start,
                    "end": seg.end,
                    "title": self._summarize_text(" ".join(current_text)[:100])
                })
                current_chapter_start = segments[i + 1].start
                current_text = []

    return chapters

def _summarize_text(self, text: str) -> str:
    """Extract first sentence as chapter title."""
    sentences = text.split(". ")
    return sentences[0][:50] if sentences else "Chapter"

Speaker Diarization

from pyannote.audio import Pipeline from dataclasses import dataclass

@dataclass class SpeakerSegment: speaker: str start: float end: float text: Optional[str] = None

class SpeakerDiarizer: def init(self, auth_token: str): self.pipeline = Pipeline.from_pretrained( "pyannote/speaker-diarization-3.1", use_auth_token=auth_token )

def diarize(self, audio_file: str) -> List[SpeakerSegment]:
    """Identify different speakers."""
    diarization = self.pipeline(audio_file)

    segments = []
    for turn, _, speaker in diarization.itertracks(yield_label=True):
        segments.append(SpeakerSegment(
            speaker=speaker,
            start=turn.start,
            end=turn.end
        ))

    return segments

def merge_with_transcript(
    self,
    speaker_segments: List[SpeakerSegment],
    transcript_segments: List[TranscriptSegment]
) -> List[SpeakerSegment]:
    """Assign speakers to transcript segments."""
    result = []

    for trans in transcript_segments:
        # Find overlapping speaker segment
        best_speaker = None
        best_overlap = 0

        for spk in speaker_segments:
            overlap = min(trans.end, spk.end) - max(trans.start, spk.start)
            if overlap > best_overlap:
                best_overlap = overlap
                best_speaker = spk.speaker

        result.append(SpeakerSegment(
            speaker=best_speaker or "Unknown",
            start=trans.start,
            end=trans.end,
            text=trans.text
        ))

    return result

Show Notes Generation

from typing import List from dataclasses import dataclass

@dataclass class ShowNotes: title: str description: str highlights: List[str] timestamps: List[dict] links: List[dict] guests: List[dict]

class ShowNotesGenerator: def init(self, llm_client): self.llm = llm_client

def generate(
    self,
    transcript: str,
    episode_title: str,
    guest_names: List[str] = None
) -> ShowNotes:
    """Generate show notes from transcript."""

    # Generate description
    description = self._generate_description(transcript)

    # Extract highlights
    highlights = self._extract_highlights(transcript)

    # Generate timestamps
    timestamps = self._generate_timestamps(transcript)

    # Extract mentioned links/resources
    links = self._extract_resources(transcript)

    return ShowNotes(
        title=episode_title,
        description=description,
        highlights=highlights,
        timestamps=timestamps,
        links=links,
        guests=[{"name": name} for name in (guest_names or [])]
    )

def _generate_description(self, transcript: str) -> str:
    """Generate episode description."""
    prompt = f"""
    Write a compelling 2-3 paragraph description for this podcast episode.
    Focus on the main topics discussed and key takeaways.

    Transcript excerpt:
    {transcript[:3000]}
    """
    return self.llm.generate(prompt)

def _extract_highlights(self, transcript: str) -> List[str]:
    """Extract key highlights from episode."""
    prompt = f"""
    Extract 5-7 key highlights or takeaways from this podcast episode.
    Format as bullet points.

    Transcript:
    {transcript[:5000]}
    """
    response = self.llm.generate(prompt)
    return [line.strip("- ") for line in response.split("\n") if line.strip()]

def _generate_timestamps(self, transcript: str) -> List[dict]:
    """Generate chapter timestamps."""
    # This would use the transcript with timing info
    return []

def _extract_resources(self, transcript: str) -> List[dict]:
    """Extract mentioned resources and links."""
    prompt = f"""
    Extract any books, websites, tools, or resources mentioned in this podcast.
    Format as: Name | Type | URL (if mentioned)

    Transcript:
    {transcript[:5000]}
    """
    return []

def to_markdown(self, notes: ShowNotes) -> str:
    """Format show notes as markdown."""
    lines = [
        f"# {notes.title}",
        "",
        notes.description,
        "",
        "## Key Highlights",
        ""
    ]

    for highlight in notes.highlights:
        lines.append(f"- {highlight}")

    if notes.timestamps:
        lines.extend(["", "## Timestamps", ""])
        for ts in notes.timestamps:
            lines.append(f"- [{ts['time']}] {ts['title']}")

    if notes.links:
        lines.extend(["", "## Resources Mentioned", ""])
        for link in notes.links:
            lines.append(f"- [{link['name']}]({link.get('url', '#')})")

    if notes.guests:
        lines.extend(["", "## Guests", ""])
        for guest in notes.guests:
            lines.append(f"- {guest['name']}")

    return "\n".join(lines)

RSS Feed Management

from feedgen.feed import FeedGenerator from datetime import datetime from dataclasses import dataclass from typing import List, Optional

@dataclass class PodcastEpisode: title: str description: str audio_url: str pub_date: datetime duration: int # seconds episode_number: int season: int = 1 image_url: Optional[str] = None explicit: bool = False

@dataclass class PodcastFeed: title: str description: str author: str email: str website: str image_url: str category: str language: str = "en" explicit: bool = False

class PodcastRSSGenerator: def init(self, feed_info: PodcastFeed): self.feed_info = feed_info self.fg = FeedGenerator() self._setup_feed()

def _setup_feed(self):
    """Configure feed metadata."""
    self.fg.load_extension('podcast')

    self.fg.title(self.feed_info.title)
    self.fg.description(self.feed_info.description)
    self.fg.author({'name': self.feed_info.author, 'email': self.feed_info.email})
    self.fg.link(href=self.feed_info.website, rel='alternate')
    self.fg.logo(self.feed_info.image_url)
    self.fg.language(self.feed_info.language)

    # iTunes specific
    self.fg.podcast.itunes_category(self.feed_info.category)
    self.fg.podcast.itunes_author(self.feed_info.author)
    self.fg.podcast.itunes_owner(
        name=self.feed_info.author,
        email=self.feed_info.email
    )
    self.fg.podcast.itunes_image(self.feed_info.image_url)
    self.fg.podcast.itunes_explicit('yes' if self.feed_info.explicit else 'no')

def add_episode(self, episode: PodcastEpisode):
    """Add episode to feed."""
    fe = self.fg.add_entry()

    fe.title(episode.title)
    fe.description(episode.description)
    fe.enclosure(episode.audio_url, 0, 'audio/mpeg')
    fe.published(episode.pub_date)
    fe.podcast.itunes_duration(episode.duration)
    fe.podcast.itunes_episode(episode.episode_number)
    fe.podcast.itunes_season(episode.season)

    if episode.image_url:
        fe.podcast.itunes_image(episode.image_url)

    fe.podcast.itunes_explicit('yes' if episode.explicit else 'no')

def generate(self, output_file: str):
    """Generate RSS XML file."""
    self.fg.rss_file(output_file)

def to_string(self) -> str:
    """Return RSS as string."""
    return self.fg.rss_str(pretty=True).decode('utf-8')

Distribution

from dataclasses import dataclass from typing import List, Optional import requests

@dataclass class DistributionPlatform: name: str feed_url: str dashboard_url: str status: str = "pending"

class PodcastDistributor: """Manage podcast distribution to platforms."""

PLATFORMS = {
    "apple": {
        "name": "Apple Podcasts",
        "submit_url": "https://podcastsconnect.apple.com/",
        "requirements": ["RSS feed", "Apple ID", "Artwork 3000x3000"]
    },
    "spotify": {
        "name": "Spotify",
        "submit_url": "https://podcasters.spotify.com/",
        "requirements": ["RSS feed", "Spotify account"]
    },
    "google": {
        "name": "Google Podcasts",
        "submit_url": "https://podcastsmanager.google.com/",
        "requirements": ["RSS feed", "Google account"]
    },
    "amazon": {
        "name": "Amazon Music",
        "submit_url": "https://podcasters.amazon.com/",
        "requirements": ["RSS feed", "Amazon account"]
    },
    "stitcher": {
        "name": "Stitcher",
        "submit_url": "https://partners.stitcher.com/",
        "requirements": ["RSS feed", "Account"]
    }
}

def __init__(self, rss_feed_url: str):
    self.rss_feed_url = rss_feed_url
    self.platforms: List[DistributionPlatform] = []

def validate_feed(self) -> dict:
    """Validate RSS feed for distribution requirements."""
    response = requests.get(self.rss_feed_url)
    # Parse and validate feed structure
    issues = []

    # Check required elements
    required = [
        "title", "description", "language",
        "itunes:author", "itunes:image", "itunes:category"
    ]

    return {
        "valid": len(issues) == 0,
        "issues": issues
    }

def generate_submission_guide(self) -> str:
    """Generate submission guide for all platforms."""
    guide = ["# Podcast Distribution Guide\n"]
    guide.append(f"RSS Feed: {self.rss_feed_url}\n")

    for key, platform in self.PLATFORMS.items():
        guide.append(f"\n## {platform['name']}")
        guide.append(f"Submit at: {platform['submit_url']}")
        guide.append("Requirements:")
        for req in platform['requirements']:
            guide.append(f"  - {req}")

    return "\n".join(guide)

References

  • Podcast RSS Best Practices

  • Whisper OpenAI

  • PyAnnote Audio

  • FFmpeg Audio Filters

  • Podcast Hosting Comparison

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

ffmpeg-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
General

site-crawler

No summary provided by upstream source.

Repository SourceNeeds Review
General

ai-video-generation

No summary provided by upstream source.

Repository SourceNeeds Review
General

n8n-patterns

No summary provided by upstream source.

Repository SourceNeeds Review