html2md — HTML → Markdown (deck-aware, base64-safe)
A stdlib-only converter (html2md.py) tuned for two cases generic converters
choke on:
- Slide decks — a
.slide-per-page HTML presentation. A normal converter
flattens it into one wall of text; this one keeps the slide structure and
emits ## Slide NN — Title headers.
- Huge embedded HTML — decks saved as single
.html with every image and
video inlined as base64 (often 5–50 MB). markitdown and friends spend
minutes decoding megabytes of base64 into useless inline blobs; this strips
the data URIs first, then parses.
When to Use
- User mentions:
html2md, /html2md, "html to md", "html→md", "把 deck 转成 markdown", "deck 转 md"
- You have a slide-style HTML deck (each slide is a
<div class="slide"> / <section class="slide">) and want structured per-slide Markdown
- You have a bloated HTML file (multi-MB) that is mostly base64 —
markitdown hangs or produces garbage
- You want to extract the text content of a deck so an LLM can critique / revise it
When NOT to Use (use a different skill instead)
| Need |
Better skill |
| Generic non-deck HTML page |
markitdown (/markitdown) — broad format support |
| DOCX with math |
/word2md (correct OMML→LaTeX) |
| PDF → Markdown |
/any2md --pdf-backend pymupdf or /pdf-reader |
| Want to render a deck to PDF (md/html → pdf) |
/html2pdf |
| A whole web page from a URL |
the web_reader MCP tool |
How to Use
# Convert, write to a sibling .md file
PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html" "deck.md"
# Convert, print to stdout
PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html"
# Extract the base64 images/videos to disk and rewrite refs (instead of [IMAGE] placeholders)
PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html" "deck.md" --extract-assets
# Drop [IMAGE]/[VIDEO] placeholders entirely (pure text)
PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html" "deck.md" --no-assets
# Flat document — don't try to split slides
PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "page.html" "page.md" --no-slides
Always set PYTHONIOENCODING=utf-8 on Windows — Chinese/CJK decks mojibake
without it.
Arguments
| Arg |
Required |
Description |
input |
yes |
Input .html file |
output |
no |
Output .md path (default: stdout) |
--extract-assets |
no |
Save base64 blobs to <stem>_assets/ and reference them by path (default: replace with [IMAGE] / [VIDEO]) |
--no-assets |
no |
Omit asset placeholders entirely — pure text |
--no-slides |
no |
Don't split into per-slide sections; one flat document |
What It Detects
- Slide structure: any
<div class="…slide…"> / <section class="…slide…">. Reads .slide-num, .slide-title, .slide-title-en to build the ## Slide NN — Title / _EN subtitle_ header. Falls back to <!-- === NN LABEL === --> HTML comments if classes are absent.
- Semantic divs common in decks:
.stat-num + .stat-label, .phase-year/.phase-name/.phase-tag, .label/.source-link/.source-note, .maic-ms-* timeline milestones, .action-* 5W1H cards. These become readable inline Markdown (**13%** — global ATP share) instead of being lost.
- Source links:
<a class="source-link" href="…">Source: MIDA</a> → [→ Source: MIDA](url).
- Tables:
<table> → pipe tables.
- Document
<title>: becomes the top-level # Title.
Dependencies
None. Pure Python 3 stdlib (html.parser, re, base64, hashlib).
Works on any machine with Python ≥ 3.8.
Notes / Gotchas
- The slide-splitter caps each slide's inner HTML at 200 KB to defeat greedy
regex across nested
<div>s. If a slide is genuinely larger than that, raise
the limit in split_slides().
--extract-assets is the way to recover the inlined videos/images from a
bloated deck: it writes each blob to <input-stem>_assets/<kind>_<hash>.<ext>
and emits . Hashes dedupe identical assets.
- For a dark/light themed BFLab deck with
.presentation { width:1280px; height:720px } and .slide { position:absolute }, the splitter works as-is.
- This skill does not render HTML or produce PDF — for that use
/html2pdf
(Chrome headless --print-to-pdf, one PDF page per slide).
Workflow: critique-then-revise a deck
A common use of this skill is to get the text of a deck out so it can be
reviewed or rewritten:
html2md deck_embedded.html deck.md — extract clean per-slide Markdown.
- Read
deck.md + the critique notes (e.g. eunice_suggestion.md).
- Author a revised deck HTML (a new file — don't edit the original).
/html2pdf the revised HTML to PDF.
1---2name: html2md3description: html2md — HTML → Markdown (deck-aware, base64-safe)4---56# html2md — HTML → Markdown (deck-aware, base64-safe)78A stdlib-only converter (`html2md.py`) tuned for two cases generic converters9choke on:10111. **Slide decks** — a `.slide`-per-page HTML presentation. A normal converter12 flattens it into one wall of text; this one keeps the slide structure and13 emits `## Slide NN — Title` headers.142. **Huge embedded HTML** — decks saved as single `.html` with every image and15 video inlined as base64 (often 5–50 MB). `markitdown` and friends spend16 minutes decoding megabytes of base64 into useless inline blobs; this strips17 the data URIs *first*, then parses.1819## When to Use2021- User mentions: `html2md`, `/html2md`, "html to md", "html→md", "把 deck 转成 markdown", "deck 转 md"22- You have a **slide-style HTML deck** (each slide is a `<div class="slide">` / `<section class="slide">`) and want structured per-slide Markdown23- You have a **bloated HTML file** (multi-MB) that is mostly base64 — `markitdown` hangs or produces garbage24- You want to **extract the text content** of a deck so an LLM can critique / revise it2526## When NOT to Use (use a different skill instead)2728| Need | Better skill |29|------|--------------|30| Generic non-deck HTML page | `markitdown` (`/markitdown`) — broad format support |31| DOCX with math | `/word2md` (correct OMML→LaTeX) |32| PDF → Markdown | `/any2md --pdf-backend pymupdf` or `/pdf-reader` |33| Want to **render a deck to PDF** (md/html → pdf) | `/html2pdf` |34| A whole web page from a URL | the `web_reader` MCP tool |3536## How to Use3738```bash39# Convert, write to a sibling .md file40PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html" "deck.md"4142# Convert, print to stdout43PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html"4445# Extract the base64 images/videos to disk and rewrite refs (instead of [IMAGE] placeholders)46PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html" "deck.md" --extract-assets4748# Drop [IMAGE]/[VIDEO] placeholders entirely (pure text)49PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "deck.html" "deck.md" --no-assets5051# Flat document — don't try to split slides52PYTHONIOENCODING=utf-8 python ~/.claude/skills/html2md/html2md.py "page.html" "page.md" --no-slides53```5455**Always set `PYTHONIOENCODING=utf-8`** on Windows — Chinese/CJK decks mojibake56without it.5758### Arguments5960| Arg | Required | Description |61|-----|----------|-------------|62| `input` | yes | Input `.html` file |63| `output` | no | Output `.md` path (default: stdout) |64| `--extract-assets` | no | Save base64 blobs to `<stem>_assets/` and reference them by path (default: replace with `[IMAGE]` / `[VIDEO]`) |65| `--no-assets` | no | Omit asset placeholders entirely — pure text |66| `--no-slides` | no | Don't split into per-slide sections; one flat document |6768## What It Detects6970- **Slide structure**: any `<div class="…slide…">` / `<section class="…slide…">`. Reads `.slide-num`, `.slide-title`, `.slide-title-en` to build the `## Slide NN — Title / _EN subtitle_` header. Falls back to `<!-- === NN LABEL === -->` HTML comments if classes are absent.71- **Semantic divs** common in decks: `.stat-num` + `.stat-label`, `.phase-year`/`.phase-name`/`.phase-tag`, `.label`/`.source-link`/`.source-note`, `.maic-ms-*` timeline milestones, `.action-*` 5W1H cards. These become readable inline Markdown (`**13%** — global ATP share`) instead of being lost.72- **Source links**: `<a class="source-link" href="…">Source: MIDA</a>` → `[→ Source: MIDA](url)`.73- **Tables**: `<table>` → pipe tables.74- **Document `<title>`**: becomes the top-level `# Title`.7576## Dependencies7778**None.** Pure Python 3 stdlib (`html.parser`, `re`, `base64`, `hashlib`).79Works on any machine with Python ≥ 3.8.8081## Notes / Gotchas8283- The slide-splitter caps each slide's inner HTML at 200 KB to defeat greedy84 regex across nested `<div>`s. If a slide is genuinely larger than that, raise85 the limit in `split_slides()`.86- `--extract-assets` is the way to **recover** the inlined videos/images from a87 bloated deck: it writes each blob to `<input-stem>_assets/<kind>_<hash>.<ext>`88 and emits ``. Hashes dedupe identical assets.89- For a **dark/light themed BFLab deck** with `.presentation { width:1280px;90 height:720px }` and `.slide { position:absolute }`, the splitter works as-is.91- This skill **does not render HTML or produce PDF** — for that use `/html2pdf`92 (Chrome headless `--print-to-pdf`, one PDF page per slide).9394## Workflow: critique-then-revise a deck9596A common use of this skill is to get the text of a deck out so it can be97reviewed or rewritten:98991. `html2md deck_embedded.html deck.md` — extract clean per-slide Markdown.1002. Read `deck.md` + the critique notes (e.g. `eunice_suggestion.md`).1013. Author a revised deck HTML (a *new* file — don't edit the original).1024. `/html2pdf` the revised HTML to PDF.