A simple desktop app for downloading videos, playlists, and podcasts from YouTube and other sites. Built on top of yt-dlp.
- What MeadowLark Does
- Features at a Glance
- Installation
- First Launch
- How to Use
- Settings Reference
- cookies.txt — What It Is and How to Get One
- Download History
- Failed Downloads
- Pending Downloads
- Updates
- Developer Setup
- Environment Variable Reference
MeadowLark lets you save videos and audio from YouTube (and hundreds of other sites) directly to your computer. You drag a URL onto the app and the file lands in your chosen folder. That's it.
It also handles:
- Bulk playlist downloads at a scheduled interval
- Podcast feeds saved as audio files
- Age-restricted videos (when you supply a cookies.txt from your browser)
- Automatically skipping videos you've already downloaded
| Feature | What it does |
|---|---|
| Drag-and-drop downloads | Drop a URL onto the 1080p, 720p, or Audio zone to start downloading immediately |
| Playlist downloader | Point the app at a text file of playlist URLs; it downloads new entries on a schedule |
| Podcast mode | Downloads audio-only (m4a/mp3) and skips anything shorter than 3 minutes |
| Archive / skip already-downloaded | Keeps a log so videos are never downloaded twice |
| SponsorBlock integration | Skips sponsor segments when downloading podcasts |
| Mark as watched | Optionally tells YouTube a video is watched after you download it |
| Live video queue | Queues live streams and retries them automatically until they're available |
| Pending download list | Surfaces premieres/live-streams not yet available on a ⏳ button; force-download now or remove |
| Failed download list | Surfaces failures on a ⚠ button; review why each one failed and retry or dismiss it |
Windows 10/11 only.
- Go to the Releases page and download
MeadowLark-Setup-{version}.exe. - Double-click the installer and follow the wizard. You can optionally create a desktop shortcut during setup.
- Launch MeadowLark from the Start Menu or your desktop shortcut.
The first time you open MeadowLark, a short setup wizard appears and asks two questions:
- Where should videos be saved? — defaults to your
Videosfolder. - Where should podcast episodes be saved? — defaults to
Music\Podcasts.
Pick your folders and click OK. The app remembers these choices in AppData\Roaming\MeadowLark and you won't be asked again. You can change them any time in Settings → Downloads.
- Drag the URL from your browser's address bar and drop it onto one of the three drop zones in the app window:
- 1080 — saves the video at up to 1080p
- 720 — saves the video at 720p (smaller file)
- audio — extracts audio only and saves as m4a
- The status bar at the bottom shows download progress. When it says [ Ready ] again, the file is in your folder.
MeadowLark can batch-download entire YouTube playlists. It tracks which videos it has already downloaded and skips them on future runs.
Set up a playlist file:
-
Open Notepad and add one YouTube playlist URL per line. Lines starting with
#are treated as comments and skipped.# My tech videos https://www.youtube.com/playlist?list=PLxxxxxxxxxxxxxxxx # Gaming channel https://www.youtube.com/playlist?list=PLyyyyyyyyyyyyyyyy -
Save the file as
playlists.txt(or any name you like). -
In MeadowLark, open Settings → Playlists and use the Browse… button next to the relevant playlist file:
- Playlists file (1080p) — full quality video
- Playlists file (720p) — medium quality video
- Playlists file (audio) — audio only
-
Click Apply and then use the matching button in the main window (Playlists, 720 Playlists, or YT Podcasts) to run a download.
Podcast mode works just like the playlist downloader but saves audio files and filters out anything shorter than 3 minutes (so shorts and trailers are skipped automatically).
- Add YouTube channel or podcast playlist URLs to your audio playlist file (see above).
- Enable Automation → Auto-check podcasts if you want the app to check for new episodes on a schedule without you clicking anything.
- New episodes land in your Audio directory (default:
Music\Podcasts).
If a video is currently live (not yet archived), MeadowLark adds it to an internal queue and retries it every 30 minutes until the stream has ended and a recording is available. No action is required from you — just drop the URL and forget it.
Open Settings from the menu or toolbar. Click Apply to save any change.
| Setting | What it does |
|---|---|
| Video directory | Folder where downloaded videos are saved |
| Audio directory | Folder where podcast/audio files are saved |
| Video format | Container for video files: mp4 (widest compatibility), mkv, or webm |
| Audio format | Format for audio files: m4a (recommended), mp3, opus, flac, or wav |
| Mark watched on YouTube | After a video downloads, automatically marks it as watched in your YouTube account. Requires a cookies.txt file with an active login. |
| Setting | What it does |
|---|---|
| Playlists file (1080p) | Text file containing YouTube playlist URLs to download at 1080p |
| Playlists file (720p) | Same, but downloads at 720p |
| Playlists file (audio) | Same, but downloads audio only (podcast mode) |
| Cookies.txt | Path to your browser cookies export. Used for age-restricted or account gated (premium) videos and the "Mark watched" feature. See below. |
The playlist files are copied into AppData automatically when you browse and apply, so the originals can be moved or deleted.
| Setting | What it does |
|---|---|
| Drop label — 1080/720/audio | The text shown on each drop zone. Cosmetic only; doesn't change behavior. |
| Ready text | Status bar text shown when the app is idle |
| Button labels | Rename any of the three playlist/podcast buttons |
| Always on top | Keeps the MeadowLark window above all other windows |
| Auto-check for app updates | Checks GitHub for a new release once a week at startup and asks if you'd like any available update. Uncheck to opt out. |
| Setting | What it does |
|---|---|
| Auto-check podcasts | When on, the app automatically checks your podcast playlist file for new episodes |
| Check interval | How often to check, in minutes (5–1440). Default is 60 minutes. |
Some videos on YouTube, or other sites, are age-restricted or require a login. MeadowLark can use a cookies.txt file — an export of your browser's YouTube session — to download these as if you were logged in.
The same file is needed if you enable Mark watched on YouTube.
It's a plain text file containing the login tokens from your browser's session. Think of it like a temporary pass that tells websites "this is me." It does not contain your password.
Option A — Browser extension (easiest)
- Install the Get cookies.txt LOCALLY extension:
- Make sure you are logged into the sites you need an account for in that browser.
- With the sites all open, click the extension icon, and click Export.
- Save the file somewhere easy to find, e.g.
C:\Users\YourName\cookies.txt.
Option B — yt-dlp CLI (for advanced users)
yt-dlp --cookies-from-browser chrome --cookies cookies.txt --skip-download https://www.youtube.comReplace chrome with firefox, edge, or brave as appropriate.
- Open Settings → Playlists.
- Next to Cookies.txt, click Browse… and select the file you exported.
- Click Apply.
Important: MeadowLark reads the file in-place and does not copy it. If your browser extension keeps the file updated automatically (some do), MeadowLark will always use the latest version.
Browser cookies expire eventually (usually after a few weeks to a few months). If you start getting login errors or age-restriction errors, re-export a fresh cookies.txt and update the path in Settings.
YouTube gates 1080p (and higher) video streams behind a per-video GVS PO token (yt-dlp #12482). Without that token, the app can still fetch the metadata and lower resolutions, but every 1080p download fails at the media stage with:
unable to download video data: HTTP Error 403: Forbidden
MeadowLark mints the token with the bgutil-ytdlp-pot-provider plugin running in script (Deno) mode. The plugin is a pinned dependency (installed by uv sync), and the Deno runtime is auto-installed into .venv/Scripts. The provider's generate script is vendored into the repo under vendor/bgutil-pot-provider/server (pinned to the same version as the plugin); its Node dependencies (node_modules) are generated once by scripts/setup_pot_provider.py.
On launch MeadowLark probes the provider and, if anything is missing, shows a "PO Token Providers: none" warning naming the missing pieces. It looks for:
| Component | Where |
|---|---|
| Provider plugin | importable yt_dlp_plugins.extractor.getpot_bgutil_script (from uv sync) |
| Deno runtime ≥ 2.0 | deno.exe in .venv/Scripts (from uv sync) |
| Generate script | {server_home}/src/generate_once.ts |
| Script dependencies | {server_home}/node_modules |
server_home defaults to the vendored vendor/bgutil-pot-provider/server (dev) or the bundled bgutil-server dir (frozen build), and is overridable with the VID_DL_POT_SERVER_HOME environment variable.
Installer users don't need this. The packaged build (from
installer\setup.iss) bundles the Deno runtime, the provider plugin, and the generate script with itsnode_modulesalready built into the app'sbgutil-serverfolder, so 1080p works out of the box. The steps below are only for running MeadowLark from source.
The generate script is already vendored in the repo, so setup is a single command:
uv run python scripts/setup_pot_provider.pyThis runs deno install in vendor/bgutil-pot-provider/server, creating node_modules next to the vendored src/generate_once.ts, then warms Deno's module cache (see below). Run it once after uv sync — the deno install half is a no-op if node_modules already exists (unless you pass --force), while the warm-up runs every time and costs ~2s once warm. Then restart MeadowLark — the warning dialog should no longer appear, and 1080p downloads will succeed.
| Flag | Default | What it does |
|---|---|---|
--force |
off | Re-run deno install even when node_modules already exists |
--skip-warm |
off | Skip the Deno module-cache warm-up. Used by CI, where the runner's cache is thrown away at the end of the job |
The script exits 0 when the dependencies are in place, 2 when Deno or the vendored server dir cannot be found, and otherwise forwards deno install's exit code. A failed warm-up is a warning, not a failure — it prints to stderr and still exits 0.
If you keep the provider somewhere else, point
VID_DL_POT_SERVER_HOMEat thatserverdirectory (the folder containingsrc/generate_once.tsandnode_modules).
node_modules alone is not enough. Deno keeps a second cache — the npm registry payload and the transpiled TypeScript — under DENO_DIR (default %LOCALAPPDATA%\deno, ~63 MB once filled). It is not part of the repo and not shipped in the installer.
This matters because the plugin gives its script-version probe a hard 15-second budget. Against a cold DENO_DIR that probe takes ~26s and blows straight through it, so the plugin reports itself unavailable, no PO token is minted, and the download 403s — with no error naming Deno at all. Once the cache is warm the same probe takes ~1.5s.
MeadowLark fills the cache for you, so there is normally nothing to do:
- From source:
scripts/setup_pot_provider.pywarms it at the end of the run. - Installer users: the app warms it in a background thread at startup (the first launch pulls the ~63 MB; the UI stays responsive throughout, and later launches are a no-op).
If a first 1080p download still 403s on a brand-new machine, the warm-up likely has not finished — retry the download, or run uv run python scripts/setup_pot_provider.py to fill the cache in the foreground.
MeadowLark keeps two logs in its AppData folder:
- history_log.txt — a record of every successful download (title, URL, timestamp)
- error_log.txt — errors and failures
You can view recent history inside the app via the History menu item (if available in your version). The archive file (archive.txt) is what yt-dlp uses internally to skip already-downloaded videos; you normally don't need to touch it. There is an Ignore Archive? checkbox that will download a video you have previously downloaded, if you need. Be careful to uncheck it when you no longer need it, or you could accidentally download whole playlists you've already seen.
When a download fails, MeadowLark records it instead of letting it scroll past in the log. A red ⚠ N button appears in the top-right corner showing how many failures are waiting; it is hidden entirely when there are none.
Click it to open the Failed Downloads window, a list of every failed item with the time it failed, the site, the download type (1080/720/audio/playlist), and the title. Hover any row to see the error message that caused the failure.
Select a row and use:
- Retry — re-queues the download exactly as if you had dropped the URL again. Already-completed entries of a playlist are skipped via the archive, so only the failed parts download. If it fails a second time, it reappears in the list with a fresh timestamp.
- Delete — removes the item from the list without downloading it.
- Right-click → Open in Browser — opens the original URL so you can check whether the video still exists.
Retry is disabled for any record whose download type can no longer be recognised (for example, a record written by an older version); Delete still works on those.
The list lives in failed_downloads.json next to the app's other resources and survives restarts — if failures are pending when you close the app, the ⚠ button is there again at next launch.
A ⏳ N button appears in the top-right corner showing how many downloads are parked waiting to become available; it is hidden when there are none.
Click it to open the Pending Downloads window, a list of every parked item with its expected availability time, kind (live or premiere), download type, and title. Hover any row to see the error or reason why nothing has downloaded yet, or the URL.
Select a row and use:
- Download Now — force the item through the normal download pipeline immediately, ignoring its release time. If it's genuinely not available yet, it will simply re-park itself with a fresh release time.
- Remove — drop it from the list without downloading it.
- Right-click → Open in Browser — opens the original URL so you can check the video page.
The list lives in pending_queue.json next to the app's other resources and survives restarts — if pending downloads are waiting when you close the app, the ⏳ button is there again at next launch.
The app polls automatically every VID_DL_LIVE_QUEUE_CHECK_INTERVAL_MINUTES minutes (documented in the Environment Variable Reference).
MeadowLark checks GitHub for new releases once a week at startup. When an update is found, a dialog appears with a download link.
To check manually: Settings → About → Check for Updates.
To turn off automatic checks: Settings → Interface → Auto-check for app updates (uncheck).
git clone https://github.com/TheGeneCode/MeadowLark
cd MeadowLark
uv sync
uv run python scripts/setup_pot_provider.py # installs the vendored PO-token provider deps (needed for 1080p)
cp .env.example .env
git config core.hooksPath .githooks
uv run python meadowlark.pywSkipping
setup_pot_provider.pymeans 1080p downloads fail with HTTP 403 and the app shows a "PO Token Providers: none" warning at startup. See YouTube 1080p Downloads.
- Python ≥ 3.10 and uv
- FFmpeg — required for audio/podcast downloads. Install via ffmpeg.org or a package manager, and make sure it's on
PATH. - Deno — auto-installed into
.venv/Scriptswhen you runuv sync.
Advanced users can override defaults by editing the .env file in AppData\Roaming\MeadowLark\.env. Most settings are easier to change through the Settings dialog.
| Variable | Default | Description |
|---|---|---|
VID_DL_VIDEO_STORAGE_DIR |
~/Videos |
Video output directory |
VID_DL_ARCHIVE_PATH |
resources/archive.txt |
yt-dlp download archive |
VID_DL_PODCAST_MISC_OUTPUT_DIR |
~/Music/Podcasts |
Misc podcast output directory |
VID_DL_ERROR_LOG |
error_log.txt |
Error log file path |
VID_DL_HISTORY_LOG |
history_log.txt |
Download history log path |
VID_DL_RESOURCES_DIR |
resources |
Resources directory |
VID_DL_VENV_SCRIPTS |
.venv/Scripts |
Virtual environment Scripts directory |
VID_DL_HTTP_TIMEOUT |
120 |
yt-dlp HTTP timeout (seconds) |
VID_DL_SOCKET_TIMEOUT |
120 |
yt-dlp socket timeout (seconds) |
VID_DL_HTTP_REQUEST_TIMEOUT |
5 |
External API request timeout (seconds) |
VID_DL_MAX_FRAGMENT_RETRIES |
10 |
Max fragment retry attempts |
VID_DL_PODCAST_MIN_DURATION_SECONDS |
180 |
Minimum duration to count as a podcast |
VID_DL_SPONSORBLOCK_CACHE_TTL_HOURS |
6 |
SponsorBlock cache TTL |
VID_DL_LIVE_QUEUE_CHECK_INTERVAL_MINUTES |
30 |
Live queue polling interval |
VID_DL_PODCAST_LOOKAHEAD_MAX_ATTEMPTS |
5 |
Max lookahead attempts for podcast fetching |
VID_DL_MERGE_OUTPUT_FORMAT |
mp4 |
Merge output container format |
VID_DL_APP_UPDATE_AUTO_CHECK |
true |
Check for a new app release once per week at startup (set to false to opt out) |
VID_DL_APP_UPDATE_LAST_CHECKED |
(empty) | ISO date of the last automatic update check; written by the app, not normally set by hand |
VID_DL_MARK_WATCHED |
false |
Auto-mark downloaded YouTube videos as watched via cookies session (requires valid cookies.txt) |
VID_DL_POT_SERVER_HOME |
vendor/bgutil-pot-provider/server (dev) / bundled bgutil-server (frozen) |
PO-token provider server home (bgutil script-deno mode); must contain src/generate_once.ts and node_modules (run scripts/setup_pot_provider.py). See YouTube 1080p Downloads. |