Package: pod-store
pod-storepod-storeis an encrypted CLI podcast tracker that syncs across devices usinggit. Inspired bypass, "the standard unix password manager."The state of your podcasts and episodes is tracked in a JSON-structured store file.gitis used to share that store file between devices.GPGkeys encrypt the store file for security.Synchronization and encryption features are optional.pod-storecan be used as an unecrypted CLI podcast tracker for a single device.This is a very young/alpha-stage project. Use at your own risk, of course.RequirementsWritten for Linux environments running Python 3.7 and above. May work on MacOS but probably does not work on Windows. Apart from Python library requirements,pod-storerequiresgit(for syncing across devices) andgpg(for encryption).Why?When I was looking for CLI podcast trackers I did not love any of the options I found.passhas been my password manager for a while now, and the concept of apass-like interface for podcast tracking appealed to me.In particular, I like thatpass:Mimics core utilities commands in name and (some) parameters where sensible (ls,rm, etc)Handles syncing across devices with a standard version control system (git)Provides security using a basic public/private key encryption standard (gpg)There are other things about thepassphilosophy that I obviously ignore in this project. In particular, I do not aspire forpod-storeto be "the standard unix"anything. That frees me from having to write it in shell script.InstallationInstall the current release version usingpip:pip install pod-storeOr install the cutting edge directly from the repo usingpip:pip install git+https://gitlab.com/ghostcat2/pod-store.gitI recommend you install this in a Pythonvirtual environment.Usagepod-storetracks your podcast data in a JSON file (which I will refer to as "the store"). To get started, set up your store. If you have already have a remotegitrepo you want to sync your podcasts with, you can provide that directly during set up. Currently only SSH authentication is supported. For encrypting the store, pass in the GPG ID from your keychain that you want to use:pod init --git-url <git repo> --gpg-id <gpg key>Leave off thegit-urloption and you can set up your remotegitpath manually. Leave off thegpg-idoption and your store will not be encrypted.You can avoid setting upgitwith your store at all using the--no-gitflag:pod init --no-gitOnce your store is set up you will want to add a podcast to it. Supply the name you want to use in the store for this podcast, and the RSS feed URL for the podcast episodes:pod add <podcast-name> <rss feed url>Optionally podcasts can reverse the order episodes appear in the list (with the newest episode appearing last, and the oldest episode appearing first):pod add -r <podcast-name> <rss feed url>You can list which podcasts in your store have new episodes, or list all podcasts in your store:pod ls
pod ls --allGet more detail with anylscommand by adding the--verboseflag:pod ls --verbose --allList new episodes, list all episodes, list new episodes for a specific podcast, list data for only a single podcast episode:pod ls --episodes
pod ls --all --episodes
pod ls -p <podcast-name>
pod ls -p <podcast-name> -e <episode-number>Refresh episode data for all podcasts from their RSS feeds, or just a specific podcast:pod refresh
pod refresh -p <podcast-name>Sometimes you may want to save data about a podcast in the store, but not continue refreshing the podcast from the RSS feed:pod set-inactive <podcast-name>Usingpod refreshon a specific podcast will force a refresh, even on a podcast you have set asinactive.To reactivate a podcast and resume updating it from RSS feed data regularly:pod set-active <podcast-name>Download all new episodes, or new episodes for just a specific podcast, or episodes for podcasts with a certain tags, or episodes for podcasts without certain tags, or episodes with certain tags, or just a single episode:pod download
pod download -p <podcast-name>
pod download -pt <tag-name>
pod download -up <tag-name>
pod download -t <tag-name>
pod download -p <podcast-name> -e <episode-number>By default podcast episodes will be downloaded to e.g./home/<username>/Podcasts/<podcast-name>/<001-episode-title>.mp3. See the configuration section for how to adjust the download path.A rudimentary locking system prevents store data from being modified by more than one command at a time. But if a data-modifying command crashes without exiting gracefully, the lock may not get released. In that case you can always unlock the store manually:pod unlockSometimes you may want to mark an episode as being not-new without actually downloading it. Do that using themark-as-oldcommand. By default you will interactively choose which episodes to mark, or you can bulk-mark all episodes. Either of these strategies can be applied toallnew episodes, or just the episodes of a specific podcast:pod mark-as-old
pod mark-as-old --bulk

pod mark-as-old -p <podcast-name>The reverse can be accomplished as well, if you want to download episodes that have been previously marked as "old". All the same command options apply:pod mark-as-newRename a podcast in the store:pod mv <old-name> <new-name>Remove a podcast from the store:pod rm <podcast-name>Run an arbitrary git command within thepod-storerepo:pod git pushEncrypt a store with keys from your GPG keyring:pod encrypt-store <gpg-id>This command works either to encrypt a previously-unencrypted store, or to switch which GPG keys are used to encrypt the store.Unencrypt a store that is set up as encrypted:pod unencrypt-storePodcasts and episodes can be marked with tags. Thetagcommand allows tagging a single podcast or episode:pod tag -p <podcast-name> -t <tag-name>
pod tag -p <podcast-name> -e <episode-number> -t <tag-name>You can also tag groups of podcasts, or episodes, or episodes for a particular podcast:pod tag --podcasts -t <tag-name>
pod tag --episodes -t <tag-name>
pod tag --episodes -p <podcast-name> -t <tag-name>By default, thetagcommand works on groups of items. By default, it tags episodes.When tagging groups of items, you can interactively select which items to tag or apply the tag to the whole group by using theinteractiveandbulkmode options:pod tag --podcasts --interactive -t <tag-name>
pod tag --episodes --bulk -t <tag-name>By default, group tagging operations are run in interactive mode. Bulk mode tagging operations will prompt the user for a single confirmation, unless you pass in theforceflag:pod tag --episodes --bulk --force -t <tag-name>You can tag ranges of episodes for a podcast by specifying the starting/ending episode numbers:pod tag -p <podcast-name> --start <episode-number> --end <episode-number>Any of the tagging commands can be used to remove tags (rather than apply them) using theuntagoption:pod tag --untag --episodes -t <tag-name>The details of a podcast can be edited:pod edit <podcast-name> -f <feed url>Feed URL and whether to reverse the episode order (or not) are configurable using this command.Configurationpod-storeallows the user to override some default behavior by setting env vars:POD_STORE_PATH  # defaults to /home/<username>/.pod-store
POD_STORE_FILE_NAME  # defaults to "pod-store.json" if not encrypted, "pod-store.gpg" if encrypted
POD_STORE_PODCAST_DOWNLOAD_PATH  # defaults to /home/<username>/Podcasts
POD_STORE_PODCAST_REFRESH_TIMEOUT  # seconds to wait before timing out a podcast RSS refresh. defaults to 15 seconds
POD_STORE_EPISODE_DOWNLOAD_TIMEOUT  # seconds to wait before timing out an episode download. defaults to the value of `POD_STORE_PODCAST_REFRESH_TIMEOUT`
POD_STORE_GPG_ID_FILE  # defaults to <POD_STORE_PATH>/.gpg-id
POD_STORE_SECURE_GIT_MODE  # set this to remove some identifying information from git commit messages
POD_STORE_EXTREME_SECURE_GIT_MODE  # set this to use hashes of the current timestamp as git commit messages to remove all identifying information
DO_NOT_SET_POD_STORE_EPISODE_METADATA  # set this to prevent setting metadata on downloaded episodesThe default GPG ID file is automatically included in the git repo's.gitignorefile. If you want to track it for some reason you can remove the entry from the.gitignorefile (or remove the.gitignorefile entirely).ContributingFeel free to file issues on Gitlab or open pull requests. Since this is a personal project I do in my spare time I am not going to work much on stuff that doesn't interest me, but I am open to any bug reports/feature requests/contributions offered in a friendly spirit.To work on the code:Fork this repo on GitlabClone your copy of the repopip install -r requirements.txtinto your development environmentMake a branch for your changes. If it is targetted at an existing Gitlab issue, name the branch in the style012-change-these-things, where012is the zero-padded three digit Gitlab issue number andchange-these-thingsis a short description of what you are working on.When you are finished, open a PR from your fork and branch into themainbranch on this repo.Write tests for your changes!The CLI is built using theClicklibrary, so some familiarity with that library will help in understanding/contributing to the code.This project usesflake8for linting,blackfor code formatting, andhttps://pycqa.github.io/isort/for import standards.PEP-8is generally followed, with the exception of an 88 character line limit rather than 79 characters (which is in line with the default behavior forblack).Tests are run usingpytestand run against multiple Python versions usingtox.Code will not be accepted that doesn't pass the test suite or the code style checks. You can run the linters and tests yourself locally before opening the PR. These commands should do it (run from the root directory of the git repo):flake8 .
black .
isort .
pytest