CLI tool
xgi/castero avatar
xgi/castero

castero: a terminal podcast client with queues, filters and playback speed

TUI podcast client for the terminal

685 stars40 forksPythonMIT

At a glance

What is it?
castero is a curses based TUI podcast client that runs in a terminal window, talks to your media player of choice for playback, keeps its feed database in sqlite, and imports and exports OPML so you can move subscriptions from another client. It suits somebody who lives in the terminal and wants a podcast player with no browser, and it does not suit somebody who wants artwork, discovery or a synchronised listening position.
Who is it for?
Adopt castero if you want a podcast client that runs inside your terminal, with a queue, offline saving and OPML transfer, and you already have VLC or mpv on the machine. Do not adopt it if you need artwork, recommendations, multi device sync or a phone client, because the project is a text interface for the desktop.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Activity is slowing. The repository last received commits 6 months ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A podcast client that is only a text interface

castero is a TUI podcast client for the terminal, and the entire product is that sentence taken literally. There is no window, no artwork, no web view and no account.

What the client does have is a design that treats the terminal as the interface rather than as a fallback. The help screen is a table of commands, and the list is long enough to be worth reading. Feeds can be added, deleted and reloaded, and an episode can be saved for offline playback. Menus can be navigated with the arrow keys, scrolled a page at a time, filtered with a slash, and inverted in order. The queue can be added to, cleared, and stepped through.

Playback has the controls you would expect from a media player: pause and play, seek forward and backward, volume up and down, and playback speed changes with the square bracket keys. There is also a command to show the URL of the current episode, which is the kind of detail a text client can offer that a graphical one tends to hide behind a menu.

The last entry in the list is the one that surprises people most: the digits 1 to 5 change between client layouts. In a terminal application, the screen is a scarce resource, and being able to switch between a feed list, an episode list and a now playing view with a key press is the kind of thing the format does well.

It is a project with a Hacktoberfest tag and a curses based interface, and it fits neatly into a workflow where the terminal is already open.

Playback is delegated to VLC or mpv

castero does not decode audio itself. Running it requires external dependencies, and the list is short: Python 3.5 or newer, which you can check with python --version, sqlite3, and at least one of two media players, either vlc 2.2.3 or newer, or mpv with libmpv at 0.14.0 or newer.

That delegation is a design decision with real consequences. The client keeps the interface, the feed database and the queue, and hands the actual playback to a program that already knows how to decode every codec under the sun. You get VLC's or mpv's codec support, their gapless behaviour and their ability to stream a URL, and castero stays a small Python program rather than a media engine.

The cost is that one of those players has to be installed and working before anything plays, and that the client inherits the player's configuration. The dependency list in the package manifest shows the shape of that arrangement, with python-vlc and python-mpv as the two playback bindings, one for each supported player.

The same separation shows up in the rest of the dependencies. requests and grequests handle the network, lxml and beautifulsoup4 parse the feed documents, cjkwrap handles text wrapping for wide characters, and pytz handles time zones. The parsing stack is there because a podcast client is mostly a feed reader, and the feed format is full of the messy corners that an HTML parser has to survive.

Installing from PyPI, and the manual route when you want the source

The normal install is a single pip command:

bash
$ pip3 install castero

Upgrading is the same command with a flag:

bash
$ pip3 install castero --upgrade

The manual route clones the repository and runs the setup script, and it is the one to use if you want to read the code before it runs on your machine:

bash
$ git clone https://github.com/xgi/castero
$ cd castero
$ sudo python setup.py install

Once installed, the client is started by name:

bash
$ castero

The console script is registered in the package manifest, mapping the castero command to the main function in castero.__main__, so the entry point behaves like any other installed command line tool.

The repository also ships a requirements file with pinned versions, and that list is worth reading before you install. It pins python-vlc at 3.0.12118, python-mpv at 0.5.2, requests with the socks extra at 2.24.0, grequests at 0.6.0, CJKwrap at 2.2, lxml at 4.9.1, beautifulsoup4 at 4.10.0, pytz at 2021.3, and for development pytest 7.1.2, coverage 6.0.2 and flake8 4.0.1.

Feeds, episodes and the database live in two directories

The configuration and the data are separated, and both are created for you.

The configuration file is at {HOME}/.config/castero/castero.conf, and it exists only after the client has been run at least once, because the client writes it on first start. The documentation points at the default castero.conf in the package templates directory for the list of available settings, which is the file to read before changing anything, since the generated file carries whatever the defaults are.

The user data, including downloaded episodes and a database with your feed information, sits at {HOME}/.local/share/castero/. The documentation is explicit that these files are not intended to be manually modified, and it explains the one recovery case that does come up: removing the database simply causes castero to replace it with an empty one the next time you run the client.

That last point is worth internalising before you rely on the database. It is a cache of feed information, not the source of truth for your subscriptions. The subscriptions themselves are recoverable from the OPML export, which is the mechanism the project provides for moving between clients. The import and export are driven by command line flags, and castero --help lists them.

The OPML support is the pragmatic part of the design. You can subscribe in another client, export its OPML, import it here, and the other way round, provided the other client supports the format, which the documentation asks you to check in that client's own documentation.

The package layout explains where the code is

The package manifest shows four subpackages, and they map onto the four things the client does.

The castero package itself holds the core, with castero.perspectives for the different views, castero.players for the media player wrappers, and castero.menus for the menu rendering. The manifest also sets package_data to include templates/* and templates/migrations/*, which is where the default configuration file and the database migrations live.

The entry points are the console script, the classifiers describe the environment as a console curses application, and python_requires is set to >= 3, while the README asks for Python 3.5 or newer, a small mismatch worth knowing about if you are on an old interpreter.

The rest of the repository is the usual scaffolding for a Python project of this age. There is a setup.cfg, a .flake8 configuration, a .codacy.yml for the automated code analysis service, a CHANGELOG.md, a res/ directory with the client example image, and a tests/ directory.

Testing is with pytest, and the documentation gives both the whole suite and a single file:

bash
$ python -m pytest tests
bash
$ python -m pytest tests/test_feed.py

The example file name, test_feed.py, tells you what the tests concentrate on, which makes sense for a client whose hardest job is parsing somebody else's RSS.

Commits continue, tagged releases stopped in 2022

The maintenance picture is the one thing you should check before you adopt this, and it is worth being precise about.

The repository is not archived, and the last push was on 2026-04-21, so commits are still landing. The release history, though, stops much earlier: v0.9.5 on 2022-04-03, v0.9.4 on 2021-09-17 and v0.9.3 on 2021-06-21. So the newest tagged release is more than four years old while the working tree has moved on since.

That combination has a practical effect. When you run pip3 install castero, you are installing the 0.9.5 line, not the current state of the master branch, because the version in the manifest comes from the package metadata and the tagged releases are what the index knows about. If a bug you hit was fixed in a commit after 2022, pip will not give it to you.

The pinned dependencies tell the same story from a different angle. requests 2.24.0, flake8 4.0.1, pytz 2021.3 and coverage 6.0.2 are versions from 2021, and grequests 0.6.0 has not moved in a long time. A client that parses hostile input from the internet with a years old XML stack deserves a look before you trust it with a feed you care about.

The licence is MIT, so the code is yours to read, fix and ship, and the MIT license file is in the repository. If you need a current version, the realistic route is to clone the repository and install it yourself, which the manual installation path already supports.

Where a terminal podcast client stops being enough

Three limits are structural.

The first is the medium. A text interface cannot show episode artwork, and the project does not attempt a terminal graphics approach. It also has no discovery: there is no catalogue of podcasts to browse, no recommendations, and no search beyond filtering the list of feeds you already have. The scope is subscribing to what you know about and playing it.

The second is that there is no account and no synchronisation. Nothing in the documentation describes a server, a login, a listening position that follows you between machines, or a phone client. The unit of state is the local directory described earlier, and the unit of transfer is the OPML file.

The third is the dependency on a media player, which is a feature in one direction and a constraint in the other. You get mpv or VLC's decoding and their configuration, but you also inherit the need to install and maintain a separate program, and the project names only two of them.

The alternative is a graphical podcast client, and the difference in approach is straightforward. A GUI application ships its own player, its own artwork rendering and its own discovery, and it takes over the screen. castero stays inside a terminal pane, delegates playback, and keeps the interface to a set of keys. If your listening happens in a terminal session, that is a better fit, and if it happens on a phone on a walk, it is not a candidate at all.

What castero has that most of them lack is the queue and the key map. A text client can afford a dense command set, and a queue you can step through with a single key is a feature that graphical clients keep moving into menus.

Editorial conclusion

Adopt castero if you want a podcast client that runs inside your terminal, with a queue, offline saving and OPML transfer, and you already have VLC or mpv on the machine. Do not adopt it if you need artwork, recommendations, multi device sync or a phone client, because the project is a text interface for the desktop. Verify first that the newest tagged release, v0.9.5 from 2022-04-03, is what pip gives you, that your Python is new enough for the pinned dependencies, and that the media player you intend to use is one of the two the project names, VLC 2.2.3 or newer or mpv with libmpv 0.14.0 or newer.

Frequently asked questions

How do I install castero?

Install it from PyPI with pip3 install castero, and use pip3 install castero --upgrade to upgrade. A manual install is also documented, cloning the repository and running sudo python setup.py install in the project directory.

What does castero need to run?

Python 3.5 or newer, sqlite3, and at least one media player: vlc 2.2.3 or newer, or mpv with libmpv 0.14.0 or newer. The client delegates playback to that player rather than decoding audio itself.

How do I import and export my podcast subscriptions in castero?

castero supports OPML files for transferring subscriptions between podcast clients, and the import and export are driven by command line flags, with castero --help listing them. You need to check whether the other client supports the format.

Where does castero store its configuration and episodes?

The configuration file is at {HOME}/.config/castero/castero.conf and is created after the first run, and user data including downloaded episodes and the feed database is at {HOME}/.local/share/castero/. The documentation says those files are not meant to be edited by hand, and that removing the database just makes castero create an empty one.

Is castero still being worked on?

The repository is not archived and the last push was on 2026-04-21, but the newest tagged release is v0.9.5 from 2022-04-03, with v0.9.4 and v0.9.3 before it. The licence is MIT, so the code can be taken, fixed and shipped under your own name.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. xgi/castero on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/xgi-castero.svg)](https://hysenlabs.com/projects/xgi-castero)

Was this useful?