VTPP 0.1.0

I've been chipping away at a fun idea, and it's far enough along now that I feel like sharing it.

For now, the name I've been using is "VTPP" (Video-to-Private-Podcast), and the concept is simple: it's a home server which periodically downloads videos from external source(s)[1], strips the audio from those videos, and serves that audio in a podcast feed accessible from my home network[2].

The server also supports downloading specific videos without subscribing to an entire video feed.

So far, all this has worked really well!

I don't yet intend to package this for anyone else to use or even to make the source available, but the concept is pretty simple to implement. If you like the idea, feel free to implement your own version! I'm not the idea police. No way I was the first one to think of this idea (I never bothered checking...).

Here are some screenshots of it working in action!

VTPP feed in podcast app and episode being played

Here's a quick demo of my phone playing an episode from the podcast feed.

an on-demand video being downloaded to the server and uploaded to the feed

Here's a quick demo of me requesting a video be downloaded on-demand to my feed server from my phone, followed by the audio becoming available within the podcast feed on my phone.

server status report uploaded to a private discord server

To make it easier to monitor the health of the server, I added Discord webhook support. If the server runs into trouble, it'll send error reports with logs to a private discord server I setup. And when new videos are successfully downloaded, I get a quick summary of what's been downloaded.

[1] currently, the only external source I've bothered to support is YouTube. The server is configured to look at the feeds of some explicitly listed YouTube channels and pull their latest videos.
[2] accessible from my home network and only from my home network i.e., by design, it's not exposed to the public internet. If I'm out and about, I'm not taking updates.
[3] currently, when I run out of space, the server stops attempting to fetch new videos and sends me a notification of the problem.

Problems with podcast apps

As mentioned earlier, I haven't yet been able to get my home feed working with my longtime-preferred podcast app, "Overcast", or the "Apple Podcasts" app. These apps allow you to subscribe to user-provided feed URLs, but when I try to use the address of my home server it fails with a non-specific error message saying it couldn't use the URL.

I still haven't quite figured out what the problem is, but I did spend a little time trying to investigate. Or rather, I spent a few days trying to capture the app's HTTPS traffic with Fiddler, and, when I couldn't get that to work, I ran out of ideas on how to gather data necessary to investigate where/why fetching my feed was failing. Some suspicions I have include...

  1. I believe these apps probably do cert pinning. At the very least, I'm pretty sure this is why I wasn't able to Fiddler the HTTPS traffic, and it doesn't seem totally out of the realm of possibility that these apps require HTTPS authenticated feeds (which I haven't setup yet) and possibly pin certs for a handful of well-known feed services.
  2. It's possible that these apps do some validation/sanitization of the user-provided URLs intended to guard against having the apps fetch data from malicious sources.
  3. It's possible that these apps don't handle URLs well using the hostname format I'm specifying. I've tried using addresses of the form http://{hostname}:{port}/ and http://{hostname}.local:{port}/ and neither seemed to make a difference.
    • Though it just occurs to me that I was always using and specifying a development port rather than the standard port 80 (HTTP) or 443 (HTTPS). Maybe I'll try exposing the server on port 80, omitting the port in the URL, and seeing if that changes anything.

A Thought on Security...

I am not a programmer with a lot of security expertise, but I'd be remiss to not reflect on a possible (probable?) security gap that I haven't yet addressed.

That is...

  1. the feed is currently served via HTTP and not HTTPS.
  2. the feed's address is my feed's hostname on the local network and not some reserved public internet name.

There's nothing preventing my phone from trying to reach this hostname on a network other than my home network, unexpectedly resolving the wrong host, and trying to talk to something/someone it shouldn't. Of course, the likelihood of this happening is minimal, and, in theory, the attack surface is isolated to my podcast app (which is why I haven't frontloaded addressing it), but I'd still feel more comfortable knowing the security gap wasn't there at all.

I think the right way to fix it would probably be to expose the feed from an HTTPS endpoint with a cert that chains back to some trusted root. Or maybe I could get by using a self-signed cert that I keep secure and installing that self-signed cert on my phone. Maybe there's an even further future where I could somehow setup some cert pinning such that only my self-signed cert would be trusted when trying to reach my feed (though I think that implies writing my own bespoke podcast app which is... uh... a little more work than I'm excited about).

Guess that's a problem for future-me.

Future Improvements

Speaking of the future, while I'm comfortable with where this project is right now and may let it rest for a while there are a handful of features on my wish list that I'd like to address eventually:

  1. Make more of the feed's runtime behavior configurable from the web portal (i.e., managing currently subscribed channels and shared metadata)
  2. Enable better management of the feeds from the web portal (i.e., deleting items from the feed that I no longer want/need)
  3. Make setting up the server more "plug-n-play" with docker containers
  4. Flagging some content to not be downloaded (e.g., YouTube shorts)
  5. Setting up an automatic eviction policy for older downloads

Here's to hoping I get around to this stuff!

Edit 12/13/2023: I got around to some of this stuff! Not all of it, but enough to mark a new v0.2.0. Check it out.