Alchemy RecipeIntermediateworkflow

Descript to Claude show notes in one Python script

Take a Descript project's exported transcript, feed it to Claude with a structured prompt, and get back show notes, chapter markers, and pull quotes in the format your podcast host requires. One script, 120 lines of Python, one API call.

Time saved
Saves 1 hr per episode
Monthly cost
~£20 / $25 (Descript Creator + Claude API)/mo
Published

Your podcast records and edits in Descript. You export an MP3 for your host and a transcript as SRT or TXT for accessibility. Then you sit down to write the show notes, chapter markers, pull quotes for social, and the summary for your newsletter. This is the part of the workflow that quietly eats 45-60 minutes per episode, every week, forever.

This script closes that gap. It takes Descript's exported transcript, sends it to Claude with a structured prompt that specifies exactly what your podcast host requires, and writes the output to a markdown file you can paste straight into your CMS. One script, one API call, about 20 seconds of compute.

What you'll build

A Python script that reads a Descript transcript export and produces:

  • A 2-sentence episode summary
  • A 120-word longer description
  • Chapter markers with timestamps
  • 5 pull quotes formatted for social media
  • The full SEO-friendly show notes with links to any external references mentioned
  • All of it in a single markdown file ready to publish

Prerequisites

  • A Descript account on the Creator plan or higher. The Creator plan includes unlimited transcription and the export formats we need.
  • An Anthropic API key for Claude. Sonnet 3.5 or later is fine. A typical 60-minute episode costs about 8-15 pence per run.
  • Python 3.10 or higher with the anthropic package installed.
  • About 20 minutes to set up the first time. Each subsequent episode is about 30 seconds of human effort.

How to build it

Step 1: Export the transcript from Descript

Open your finished podcast project in Descript. Click File → Export → Transcript. Choose Plain text (TXT) with timestamps as the format. This gives you a file that looks like:

[00:00:12] Alice: Welcome to The Workflow Show. Today we are talking about...
[00:00:30] Bob: Thanks for having me. I have been looking forward to this.
[00:00:45] Alice: So let's start with the question everyone asks first.

Save the export as episode-042.txt in a folder of your choice. This file is all you need. You do not need the audio, the project file, or anything else Descript produces.

Step 2: Define the show notes template

Every podcast has slightly different show notes requirements. Your host might need a specific field for "topics discussed", a field for "guest bio", a field for "resources mentioned", and so on. Define this once in a plain text template and the script will fill it in.

Create a file called template.md:

**Episode:** {{episode_number}}
**Host:** {{host}}
**Guest:** {{guest}}
**Duration:** {{duration}}

## Summary

{{summary}}

## In this episode

{{description}}

## Chapters

{{chapter_markers}}

## Pull quotes

{{pull_quotes}}

## Resources mentioned

{{resources}}

## Transcript

{{transcript_link}}

Step 3: Write the Python script

Here is the whole thing. It reads the transcript, reads the template, calls Claude with a structured prompt, and writes the output to a markdown file.

import os
import re
import json
import anthropic
from pathlib import Path

client = anthropic.Anthropic()

PROMPT = """You are a podcast show notes writer. I will give you a
transcript of an episode with timestamps. Produce the following
outputs as a single JSON object, and nothing else.

{{
  "title": "A punchy, SEO-friendly episode title under 60 characters",
  "summary": "Exactly 2 sentences describing what this episode is about",
  "description": "A 100-120 word description suitable for podcast apps",
  "chapter_markers": [
    {{"timestamp": "00:00:00", "title": "Intro"}},
    {{"timestamp": "00:03:15", "title": "First topic"}}
  ],
  "pull_quotes": [
    "A single sentence quote, attributed to the speaker, under 240 characters"
  ],
  "resources": [
    {{"name": "Resource name", "url": "https://... if mentioned, or null"}}
  ],
  "guest": "Full name of the guest if there is one, otherwise null",
  "host": "Full name of the host"
}}

Rules:
- Chapter markers should be at natural topic transitions, not every
  60 seconds. Aim for 5-10 chapters in a 60-minute episode.
- Pull quotes must be verbatim from the transcript, not paraphrased.
- Resources are things mentioned by name that a listener might want to
  look up: book titles, product names, companies, people.
- Use British English spelling (customise, analyse, colour, etc).
- Do not use em dashes. Use commas or full stops instead.

Transcript:

{transcript}
"""

def parse_duration(transcript: str) -> str:
    last_match = None
    for m in re.finditer(r"\[(\d{{2}}):(\d{{2}}):(\d{{2}})\]", transcript):
        last_match = m
    if not last_match:
        return "unknown"
    return f"{last_match.group(1)}:{last_match.group(2)}:{last_match.group(3)}"

def generate_show_notes(transcript_path: Path) -> dict:
    transcript = transcript_path.read_text(encoding="utf-8")

    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=4000,
        messages=[
            {
                "role": "user",
                "content": PROMPT.format(transcript=transcript),
            }
        ],
    )

    raw = response.content[0].text.strip()
    # Claude sometimes wraps JSON in a markdown code fence
    if raw.startswith("```"):
        raw = re.sub(r"^```(?:json)?\n", "", raw)
        raw = re.sub(r"\n```$", "", raw)

    show_notes = json.loads(raw)
    show_notes["duration"] = parse_duration(transcript)
    return show_notes

def render_template(template_path: Path, show_notes: dict, episode_number: str) -> str:
    template = template_path.read_text(encoding="utf-8")

    chapter_markers = "\n".join(
        f"- **{c['timestamp']}** {c['title']}"
        for c in show_notes["chapter_markers"]
    )

    pull_quotes = "\n\n".join(
        f"> {q}" for q in show_notes["pull_quotes"]
    )

    resources = "\n".join(
        f"- [{r['name']}]({r['url']})" if r.get("url") else f"- {r['name']}"
        for r in show_notes["resources"]
    )

    filled = template
    filled = filled.replace("{{title}}", show_notes["title"])
    filled = filled.replace("{{episode_number}}", episode_number)
    filled = filled.replace("{{host}}", show_notes["host"])
    filled = filled.replace("{{guest}}", show_notes.get("guest") or "Solo episode")
    filled = filled.replace("{{duration}}", show_notes["duration"])
    filled = filled.replace("{{summary}}", show_notes["summary"])
    filled = filled.replace("{{description}}", show_notes["description"])
    filled = filled.replace("{{chapter_markers}}", chapter_markers)
    filled = filled.replace("{{pull_quotes}}", pull_quotes)
    filled = filled.replace("{{resources}}", resources or "_None mentioned_")
    filled = filled.replace("{{transcript_link}}", "[Full transcript](transcript.txt)")

    return filled

if __name__ == "__main__":
    import sys
    if len(sys.argv) != 3:
        print("Usage: python show_notes.py <transcript.txt> <episode_number>")
        sys.exit(1)

    transcript_path = Path(sys.argv[1])
    episode_number = sys.argv[2]

    show_notes = generate_show_notes(transcript_path)
    output = render_template(Path("template.md"), show_notes, episode_number)

    output_path = transcript_path.parent / f"show-notes-{episode_number}.md"
    output_path.write_text(output, encoding="utf-8")
    print(f"Written: {output_path}")

Run it like this:

export ANTHROPIC_API_KEY=sk-ant-...
python show_notes.py episode-042.txt 42

Twenty seconds later, you have a show-notes-42.md file in the same folder. Paste it into your podcast host's episode page and ship.

Step 4: Add a spot-check loop

The first few episodes you run through this, spot-check three things:

  1. Chapter markers are at real topic transitions, not arbitrary 5-minute intervals. If they are wrong, the transcript may have been too long for the model to process well and you need to either use Claude Opus (more expensive but handles longer context better) or split the transcript into two halves.
  2. Pull quotes are verbatim. Search the transcript for each quote and confirm it is there word for word. Occasionally Claude will paraphrase slightly, which is a problem if your guest objects.
  3. Resources are actually mentioned in the episode. Claude has a tendency to hallucinate URLs if the guest mentions a book without saying the author or publisher. Check any URLs it generates and remove the ones that are not directly stated in the transcript.

After ten episodes, these checks take under a minute because you know which corners Claude tends to cut.

The script is also only as good as the transcript Descript produces. If your podcast has a lot of cross-talk, muffled audio, or a speaker with an unusual accent, Descript's diarisation will struggle and your chapter markers will be off. In those cases, correct the obvious speaker misattributions in Descript before exporting. Claude cannot recover information that is not in the input.

Cost breakdown

  • Descript Creator: £12/mo (you probably already have this)
  • Claude API: about 8-15 pence per episode on Sonnet 3.5, using roughly 25k input tokens and 2k output tokens
  • At 4 episodes per month: about 40-60 pence in API costs, total cost £12.40-12.60/mo incremental

The template system above is deliberately minimal. If your show notes need embedded images, custom HTML blocks, or integration with a specific CMS like WordPress or Transistor, adapt the render_template step to produce the right output format. Everything else in the script is reusable across podcast hosts without modification.

More Recipes