Splitting a YouTube Lesson Into One MP3 Per Chapter with yt-dlp
My father is learning Greek late in life, on walks, with the screen away. He wanted each lesson broken into its sections — one mp3 per chapter — so he could study them the way you'd practise a piece of music, one at a time. Here's how yt-dlp cuts a long video along its own seams into a numbered set of per-chapter mp3s, then does it across a whole playlist — like I did for my father.
There's a YouTube playlist my father has been following for a while now — «Греческий на Слух», Greek by Ear. He's learning Greek, slowly, the way you learn a language late in life: in the cracks of the day, on walks, with the screen put away. The videos are long. Each one is an hour or more, stitched together from segments and split into chapters: vocabulary drills, dialogues, grammar, a story told in plain Greek, the whole apparatus of a lesson.
And he wanted to study them the way you'd practise a piece of music — one section at a time. Not the whole hour in a single sitting, but the vocabulary drill on its own this morning, the dialogue tomorrow, the plain-Greek story put on repeat until the ear catches it. For that he needed each chapter as its own file: a small set of mp3s per lesson, one per section, that he could drop on his phone and take out on a walk one at a time, with the screen in his pocket.
That's the quiet hostility of a long video: the container is built so you stay. The chapters, though, are seams. Someone already marked where each section starts and where it stops. And yt-dlp will happily cut along every one of those seams at once, handing back a whole lesson as a numbered row of mp3s — one per chapter — instead of one hour-long blob. So that's what I did for him.
See what's inside first
Before cutting anything, I want to know what the seams are actually called. yt-dlp can print a video's chapter list without downloading a single byte — --print implies --simulate, so it just reads the metadata and stops.
yt-dlp --print "%(chapters)j" "https://www.youtube.com/watch?v=58uHKLmLE5U"
That %(chapters)j template gives you clean JSON — a list of {start_time, end_time, title} objects. (You can also write --print chapters, but that prints an ugly Python repr of the same thing; the j template is the readable form.) If the video has no chapters, you get an empty result, and you'll know there's nothing to split along. What I'm looking for is how many sections there are and what they're called — because in a moment yt-dlp is going to turn each one into a file named after its title.
Split a video into one mp3 per chapter
Here's the heart of it, and it rests on one flag. --split-chapters reads the video's internal chapters and writes one file per chapter; -x extracts the audio and --audio-format mp3 makes those files mp3s. yt-dlp pulls the whole audio down to a single mp3 first, then slices that mp3 along the chapter boundaries into one mp3 per section.
yt-dlp \
-x --audio-format mp3 \
--split-chapters \
"https://www.youtube.com/watch?v=58uHKLmLE5U"
There's one wrinkle worth knowing before you run it. Without a naming template, every chapter file falls back to the default output name — which is the same for all of them, derived from the video title — so yt-dlp ends up disambiguating or overwriting them in unhelpful ways. The fix is a chapter: output template, which names only the split files:
yt-dlp \
-x --audio-format mp3 \
--split-chapters \
-o "chapter:%(section_number)02d - %(section_title)s.%(ext)s" \
"https://www.youtube.com/watch?v=58uHKLmLE5U"
The chapter: prefix is the key. yt-dlp lets you set a separate output template per file type, and chapter: targets the split files specifically. The fields that exist here are the section_* group: %(section_number)s, %(section_title)s, %(section_start)s, %(section_end)s — not %(chapter)s, which is a different, unrelated field. %(section_number)02d zero-pads the number to two digits, so a lesson comes out as 01 - ..., 02 - ..., 03 - ..., in order, and a folder of them sorts the way the course intends.
Audio extraction and the split both lean on ffmpeg — that's the one hard dependency here. -x wants both ffmpeg and ffprobe on your PATH, and --split-chapters shells out to ffmpeg too, so if yt-dlp complains it can't find a binary, that's the missing piece: brew install ffmpeg on a Mac, your package manager elsewhere.
One thing to expect, because it surprised me: --split-chapters keeps the full-length file as well. You get your N chapter mp3s and the original hour-long mp3 sitting alongside them. There's no clean flag that deletes the leftover full file after splitting — the --no-keep-video/-k family governs the source media before audio extraction, not the extracted mp3 the splitter reads from, so it won't remove it. In practice you either ignore the extra file, route the chapter files into their own directory to keep them separate, or delete the full one by hand afterward. I went with a separate directory, which -P and the same chapter: prefix make easy:
yt-dlp \
-x --audio-format mp3 \
--split-chapters \
-P "chapter:./chapters" \
-o "chapter:%(section_number)02d - %(section_title)s.%(ext)s" \
"https://www.youtube.com/watch?v=58uHKLmLE5U"
Now the per-chapter mp3s land in ./chapters, and the leftover full-length file stays up in the working directory where I can glance at it once and bin it.
If you only want a single slice instead of all of them
Sometimes you don't want the whole set — you want one section, or just minutes 10 to 15. For that there's --download-sections, which fetches only the part you name and skips the rest of the download entirely.
My father has a favourite. Near the end of each video there's a chapter called «История на греческом без субтитров» — a story told in plain Greek, no subtitles, no English crutch underneath. When he just wants that one, on its own, there's no need to pull the whole hour:
yt-dlp \
--download-sections "(?i)история на греческом без субтитров" \
-x --audio-format mp3 \
"https://www.youtube.com/watch?v=58uHKLmLE5U"
The argument is a regular expression matched against each chapter's title, and the match is case-sensitive by default — yt-dlp compiles it with a bare re.search and no re.IGNORECASE, so история will not match История. The leading (?i) flag makes it case-insensitive, and across a playlist where capitalisation drifts it isn't cosmetic — it's the safe default. Escape any regex metacharacters in a title (., (, ?, +) or they'll be read as regex.
And if the part you want isn't a chapter at all but a raw span of time, prefix the argument with * and --download-sections switches to a literal time range:
yt-dlp --download-sections "*10:15-15:30" -x --audio-format mp3 \
"https://www.youtube.com/watch?v=58uHKLmLE5U"
Use inf for the end to run to the finish ("*1:30:00-inf"), and you can pass the option more than once to collect several slices at once. The cut snaps to the nearest keyframe by default — fast, but the edges may carry a second or two of extra footage; --force-keyframes-at-cuts makes them exact at the cost of a re-encode. For audio on a walk, I never cared.
Do it across the whole playlist
This is where it stops being a party trick and starts saving real time. Hand yt-dlp the playlist URL instead of a single video, and --split-chapters runs on every entry — each video gets split into its own per-chapter mp3s, independently.
yt-dlp \
-x --audio-format mp3 \
--split-chapters \
-o "chapter:%(playlist_index)s - %(title)s/%(section_number)02d - %(section_title)s.%(ext)s" \
"https://www.youtube.com/playlist?list=PLTV0lR0SKiEu51XnbiJ8vCXDLQDiFR9uh"
That template builds one folder per lesson — %(playlist_index)s numbers the lessons in course order — and inside each, the chapters numbered in turn. A whole course comes back as a tidy tree: lesson by lesson, section by section. Any video in the playlist that happens to have no chapters prints Chapter information is unavailable, produces no split files, and is passed over without error — the first time I saw that line I thought I'd broken something; I hadn't.
Tidying the names
Split-chapter filenames can come out messy — chapter titles aren't written to be filenames, so you'll find stray punctuation, doubled spaces, the occasional emoji a channel dropped into a section heading. The chapter: template fixes most of it at the source, but when something slips through I clean it up afterward with a small sed loop.
for f in *.mp3; do
new=$(echo "$f" \
| sed -E 's/^Греческий на Слух[^)]*\)[🎧. ]*//' \
| sed -E 's/ *\[[^]]+\]//' \
| sed -E 's/^[ .]+//; s/[ .]+$//')
mv -n "$f" "$new"
done
Three passes: strip the channel prefix up through the first ) and any trailing decoration, drop a [video-id] bracket if one survived, then trim stray leading and trailing dots and spaces. A second tiny loop collapses any doubled dots left before the extension:
for f in *.mp3; do
new=$(echo "$f" | sed -E 's/\.+\.mp3$/.mp3/')
mv -n "$f" "$new"
done
That first pattern is shaped to my exact filenames — a channel name followed by a parenthesised lesson marker — not a general-purpose cleaner; drop it on differently-shaped names and the prefix may survive untouched. sed -E is the portable way to ask for extended regex — it works on macOS's BSD sed and on GNU sed alike (GNU's -r is a non-portable synonym, so prefer -E). Quoting "$f" and "$new" keeps spaces from blowing the loop apart, and mv -n refuses to clobber an existing file.
A couple of edges caught me. In bash with nullglob off, if no .mp3 files exist, the loop runs once with the literal string *.mp3 as the filename — I now guard that with shopt -s nullglob. (zsh is stricter and just errors out with no matches found instead.) And mv -n skips a colliding rename silently, with no warning, so when two cleaned names landed on the same string, one file quietly kept its old name. With per-chapter splits this bites harder than you'd think — short generic section titles like «Повторение» repeat across lessons, so number them or fold them into per-lesson folders and the collisions never arise.
When the cookies break
Most of the time, reading public chapter metadata needs no authentication. But sometimes YouTube throws up a "Sign in to confirm you're not a bot" wall, or the content is age-restricted, members-only, or private. For those, yt-dlp can borrow your browser's session:
yt-dlp --cookies-from-browser chrome \
-x --audio-format mp3 \
--split-chapters \
-o "chapter:%(section_number)02d - %(section_title)s.%(ext)s" \
"https://www.youtube.com/watch?v=58uHKLmLE5U"
The browser name is one of brave, chrome, chromium, edge, firefox, opera, safari, vivaldi, whale. The failure modes that tripped me up, since they aren't obvious:
- "Permission denied" almost always means the browser is open. Quit it fully and try again.
- On macOS you may get a Keychain prompt for the "Chrome Safe Storage" key — that's expected; approve it.
- On Windows, Chrome 127 and up broke
--cookies-from-browser chromeoutright with App-Bound Encryption (you'll see a "failed to decrypt with DPAPI" error). Use Firefox instead, or export acookies.txtand pass--cookies file.txt.
The yt-dlp wiki is candid that automated access with cookies can get an account flagged. If you're doing anything at volume, use a throwaway account and export its cookies from a private window — not the account you actually care about.
A word on being decent about it
YouTube's terms restrict downloading except where specifically permitted, and restrict automated access except as robots.txt allows or with permission. The clearly fine cases are your own uploads, Creative-Commons and public-domain material, and personal or educational use of content you can already watch — that last one being a jurisdiction-dependent gray area, not a blanket licence. What I did sits in the ordinary personal-use zone: language-learning audio, broken into pieces for my father's ears, on his own walks, redistributed to nobody. Take what you need for yourself, leave the rest. Don't mirror someone's channel and call it learning.
What he actually got back
What my father has now is a folder per lesson, and inside each a numbered set of short mp3s — the vocabulary drill, the dialogue, the plain-Greek story — that he works through one piece at a time on the way to the shop and back. No browser. No autoplay deciding what comes next. No hour-long file to scrub through to reach the section he's on today. Just the lesson, taken apart into the pieces it was always made of, in a form that fits the shape of his day.
The video wanted his hour, whole and in order. I handed it back to him in sections, and he studies them one walk at a time.
Enjoyed this? Get the next one.
New essays, in your inbox. Double opt-in, unsubscribe anytime — no tracking pixels.