PodOsef – MP3-first, lightweight, simple podcast publishing system.


Over a decade ago I wrote a PHP-based podcast publishing/archive system. The idea behind it was MP3-first: no DB, no complications – just drop an MP3 file into a folder and distribute. The software could generate HTML and an iTunes RSS feed. The main point was simplicity and zero maintenance.

Over the last 10 years, a few issues cropped up. Some were fixed, and a couple of upgrades were slapped on top. However, a few issues (some major ones) remained, and last year I decided to rethink and rewrite the software.

No dream survives contact with reality

The rethinking phase was quite an enjoyable trip. Like a little kid in a candy shop, I was dreaming up anything and everything: rewrite in Java, JS, or Go; add a fancy frontend (to what purpose I wasn’t sure); p2p sync across servers; scaling; and so on.

Finally, the end of 2025 arrived, and it was time to get down to work. As pragmatism settled in, reality hit the fan: make it smaller, faster, and cheaper. Whatever it is, it must work on the smallest droplet, take as little space as possible, respond fast, and require no (or as little) maintenance as possible. With a countdown timer running, the feature set started to shrink rapidly.

Complexity & legacy

Next came a major design decision. A podcast is typically represented by an RSS feed (an XML file), which contains podcast information, a list of episodes, information about each episode, and a URL to the media file. Podcast client software reads the feed and displays it in a beautiful interface, ready for your enjoyment.

But where is the information stored? The internet is full of podcast-oriented CMSs (content management systems), podcast plugins for CMSs, and podcast services. Most of those solutions have one problem: they split information across a database, file storage, and/or config files. As a result we get complexity and, more importantly to me, legacy.

RSS feeds don’t grow infinitely. In theory they can, but in practice sooner or later the feed is trimmed and old episodes are forgotten. Well, not if I can help it – and wouldn’t it be nice to reduce system complexity at the same time?

Choose the source of truth

An audio recording (represented by an MP3 file) can be downloaded, stored, and listened to in any player (burn it to a CD for the old school). But most of the time it lacks the information provided by the RSS feed.

So what if we invert that? What if the source of truth is the MP3 file itself? All podcast information would be stored in the MP3: title, authors, show notes, and cover art. In that case, the RSS feed would simply reflect information already present in the MP3 file (episode).

The Apple Music app lets you edit MP3 metadata (ID3 tags)

In such an arrangement, no database or metadata storage system is required. No need to monitor a DB, no need to back up metadata – almost everything you need is the MP3, since everything else can be recreated from the media file itself, plus a small config for Apple RSS feed requirements.

Make it as simple as possible and no simpler

The prior version was fully dynamic and, in retrospect, that didn’t make much sense, since RSS feeds typically don’t change often. Even if you release new episodes multiple times per day, a statically generated feed is still a far better fit and costs far less.

However, to avoid pauses and polling, the inotify utility was used. It effectively triggers regeneration on file changes, which avoids unnecessary polling and updates the RSS feed as soon as a new episode is uploaded.

Hugo – a static site generator – seemed like a good fit for the task. It’s small, fast, and supports RSS out of the box. In addition, Hugo turned out to be fairly easy to work with, even though I didn’t need much in the way of templating.

Next, it was time to deal with the source of truth. As far as MP3 goes, it has enough pockets to stash your information into. Different versions of software save metadata into different fields, so to accommodate them all – and make it as bulletproof as possible – ExifTool was chosen. It can read metadata from different media formats and write it into metadata files, which are then used by Hugo to generate HTML and RSS.

Main & archive

Now that complexity is addressed, let’s get back to legacy. As I mentioned before, sooner or later podcast episodes are trimmed and, well, lost – unless someone preserves them. In this case it’s fairly easy to organize an archival version of the podcast. Since all the information is contained within the MP3 file itself, all that’s left is to download it.

Taking this idea a little further: an archive can be organized simply by deploying PodOsef along with a downloader (already built). It literally watches the main RSS feed, downloads MP3s, and with a little configuration you’ve got yourself an archival (so to speak) version that keeps downloading new episodes and keeping old ones.

This can be used as a cheap form of scaling, or perhaps community-driven archiving. However, I haven’t put much thought into it, and I have a feeling there might be a better way to scale – but at what cost, I don’t know.

And so…

We got ourselves an MP3-first, simple, lightweight, cheap, minimalistic podcast publishing system – with an archival option – that’s containerized under 250 MB and can run on a small server/instance/droplet, or even on an old machine. 

PodOsef is open source. You can modify it as you see fit, or drop me feedback. While I can’t promise feature fulfillment, I sure do appreciate it. For more technical information, please go to the GitHub repo: https://github.com/meirka/podOsef.

Bad luck, that’s all it was

Today was supposed to be another workday—routine and busy. I had a few things I wanted to finish, but some unfortunate news came in the morning. Not unexpected, but it pushed my already gloomy mood to the bottom and drove my thoughts even deeper into an endless state of wondering.

I’ve been wondering and pondering the meaning of life for a little while now… I don’t know, and I might never know. But last year, as my neighbour was gearing up to go to Mexico for the winter, I got a bad feeling. I don’t know how to describe it, but it felt like he was taking his last trip… like I might never see him again. I don’t know why, I don’t know how—it was just a stupid feeling.

He went to Mexico many times before—no issues—and every spring he would come back and things would carry on. Not this time. He got an infection sometime prior (bad luck), and the troubles started on the way to Mexico. As far as I know, he tried to get help in Mexico, but none of it worked, and he was back in Canada a month later.

Don was my incredibly awesome neighbour for nearly a decade. He’s been in the hospital for the past couple of months, slowly withering away, while I’ve been hoping for a miracle. The first time I visited him in the hospital, I had my hopes up. I hoped he would recover—that we’d spend at least one more summer having drinks on odd evenings, and he’d laugh at my problems, occasionally dispensing wisdom in the form of his seemingly endless life stories, or just telling jokes and making fun of the world.

I don’t exactly recall how we met the first time, but I’m sure he invited me for a drink, and that’s how our relationship began. Since then, we hung out quite a bit. He had an interesting character: on one hand stoic and nonchalant, on the other fiery and determined. It takes courage to call out stupidity right to your face, and he did—every time I cooked up yet another “brilliant” idea, like building a shed out of 1×2 or 2×2 wooden planks. Needless to say, his advice didn’t go unnoticed (I didn’t build that shed). He had a lot of experience, and I was happy to receive some of it.

I really appreciated his take on my issues (and oh boy, I’ve got some), especially when my aunt passed away. He told me to go and pay my respects no matter who said what and why. In retrospect, he was absolutely right.

I grew up without a dad, and my mom did everything she could for me. I never really dwelled on it until fairly recently. On one warm, breezy night, drinking with Don and talking about life, I started to wonder if that’s how it should’ve been. Moms are always protective, but what about dads?

I didn’t really have an answer, but what I could gather is that dads seem to give you that tough pill to swallow: if you’re right, you’re right—and if you screwed up, then you’re wrong, and let’s fix it. I don’t know how true my idea is, but that’s how I perceived Don. He was always helping whenever I needed it: advice, tools, knowledge, even a hand when he could.

He always had a story to tell, advice to spare, and he never pretended to be any more than what he was—French Canadian with a big heart and a soul.

Poor Man’s PiTest

Unit testing, as important as it is, is still treated as an afterthought. First, we write production code and then tests — if we have enough time and, more importantly, the will. Unfortunately, the natural result of this process is missing coverage, weak or absent assertions, and, more importantly, a lack of real exercise of the production code. I believe these issues are well known, but somehow they’re still not important enough to change the process. After all, we have production code; let’s ship it — “Damn the torpedoes, full speed ahead!”


In my opinion, this issue is well addressed by Kent Beck and Robert C. Martin (Uncle Bob), who emphasize starting with a test. Unfortunately there seems to be lots of fluff, buzz and general misunderstanding of TDD. No, it is not Test-First. No, there is no magic sauce. No, there is no need to read tons of books or go to fancy lectures. After reading a few books, watching a few videos, attending a few fancy lectures and a few years of relentless practice, I can definitely say that all you essentially need is to read Kent Beck’s book – Test-Driven Development: By Example, watch Robert C. Martin and stick to The Three Rules of TDD. While Kent and Robert do not address every single scenario and issue, the above resources are more than enough to extrapolate and adapt the technique, let’s say to microservice architecture (which is not originally addressed). Now I’m not saying those are all the resources that you will ever need, but those are essential for TDD! Last but not least, if you don’t know how to do dependency injection without @Autowire (unfortunately I’ve seen that), it does not mean TDD sucks, it means you lack knowledge. TDD is not magic or a Swiss Army knife, it is a specialized technique for writing code independent of any specific platform, framework or language.

Example of useless 100% code coverage

Unfortunately TDD is not widely adopted, and even if it was, we are still subject to mistakes — we are only human. Under these conditions we need a solution that does not rely on a human. One bad solution is code coverage. Code coverage has been around forever and is loved by managers as it neatly translates to charts, means little, and can easily be faked by developers. Code coverage has its uses, but it is useless in determining how well production code is actually tested. But don’t despair, we do have a solution — mutation testing. Mutation testing has a simple premise: change production code, see if any tests fail. If nothing is failing after the change, that means insufficient testing. In an inverted way it sure resembles TDD. While the premise is simple, execution is quite expensive! See how long your test set takes, then see how many logical and state changes can be applied in production code, multiply those numbers and you see how much time mutation testing will take — in short, a while for a sufficiently large project. That is probably the reason we don’t see mutation testing everywhere.

Mutation testing is hard to fool

Fortunately the Java world is blessed with a modern tool for mutation testing – PiTest. It provides lots of features in its open source form. Even more features come with the commercial extensions, which integrate into a modern pipeline (or dev machine) and provide quick feedback to developers waiting for code review on their code changes. I wholeheartedly suggest buying a license and using the full power of mutation testing if code quality is important. Unfortunately my company, for various reasons, isn’t quick to sign up, despite claims to strive for quality. After a proof of concept, presentations, near begging and a final rejection, I fell into depression. I really wanted to use PiTest for tangible results and partly because it would have made my life a lot easier and the life of junior developers a lot harder – since I would not have to spend any time figuring out if their test code actually tested enough or anything at all (yeap, I’ve seen those cases too).

Finally, I realized I’m an engineer; my realm of possibilities lies far beyond microservices. I decided to build a “Poor Man’s PiTest”: a small tool that takes the open-source core and combines it with the power of Bash, Git, and a few other Linux commands. That gave me the ability to build only the projects that are needed, run PiTest only on the changed classes in a multi-module Maven project with a mix of JUnit 4 and JUnit 5 modules, perform timing analysis, log everything for debugging purposes, and optionally skip builds and/or focus on a single module if needed. So far it works well and, let me tell you, junior developers just “love it” — especially when I give them a long report of everything that’s been missed.

I figured someone might be in a similar situation, perhaps needs a bit of inspiration or an example, so I created a GitHub repository to share the script.

Thank you, and I hope the script will be of some use.

15 years or 300k kilometres with Accent

It’s hard to believe, but it’s been almost 15 years since I bought my Hyundai Accent. Quite a few things happened along the way: marriage, fatherhood, a house, Covid, a Miata, the war in Ukraine, October 7th, economic ups and downs — but one thing remains the same: my main drive.

I’m not sure where to start. I guess I’ll start at the beginning: I bought the Accent because my old car was pretty worn out and I had just gotten my first corporate job. I needed to show up every day, and to do so, I had to drive a bit of a distance on the highway. My main criteria were quite simple: reliability and economy. After some consideration and monetary incentives from Hyundai, I purchased the Accent for $16,500 Canadian dollars out the door. Looking at today’s market and back at that price, I can’t believe I got a new car for $16.5K all inclusive! Oh, and it wasn’t a base trim either — it was the GL trim (power windows, AC, cruise control) with automatic transmission. I know those features don’t look like much nowadays, but back then they were all options, especially for compact cars.

The car performed very well, considering that during the week I averaged around 100 kilometres per day and every other weekend took a trip to Toronto. So the miles were adding up quite rapidly. Ah, and the road trips — the biggest one was across the USA, from Detroit to New Orleans to Florida and back. I maintained the car rigorously but didn’t baby it. A few years back, I even used it for construction — loading up bags of gravel and concrete blocks in the back. The car looked like it was lowered, and I thought the shocks would finally give out — and yet, here it is, still on the original shocks. Are they in good shape? Nope. But they haven’t blown yet!

Funny enough, as inconvenient as a 3-door hatchback might look when it comes to children, in a way it’s quite safe — no doors (or windows) to open in the back — security by default. Somehow we got through all the baby seats without much hassle. Yes, the car is small and there are no back doors to open and stick a child in, but somehow it never bothered me.

As far as repairs go, there were a few. Some due to mileage and some due to stupidity on my part. I replaced the power steering fluid with the incorrect one… well, the car didn’t like it. I didn’t realize it and figured the steering pump went bad, so I replaced it — to no avail. Once the fluid got replaced, it became painfully evident that the new pump was completely unnecessary… oops. I replaced the valve gasket that started leaking sometime last year or two ago, but I didn’t notice it until the throttle cable snapped. Both jobs were pretty easy and DIY-friendly, even though the gasket took a lot longer since I wasn’t entirely sure what I was doing.

Speaking of DIY, the Accent is pretty simple and straightforward — you can do all of the maintenance yourself and even some minor repairs. Moreover, since the drivetrain is small, it doesn’t take much — engine oil capacity is about 3.4 litres. That leads me to an interesting thought: since original parts last so long and most of the work can be DIYed, you can purchase spare parts from the dealership. Yes, they cost considerably more, but you know they’ll last and, more importantly, fit properly when you install them yourself.

I must add one honourable mention: the Accent loves ignition coils — can’t get enough of them. Each coil lasts about 70,000 kilometres, then it burns out. So I always keep one in the car, just in case a coil decides to give out.

I must admit, the Hyundai Accent is a no-frills car — super basic, without even ABS or traction control. But in a way, that makes it quite unique nowadays. I wish I had bought it with a manual transmission; it would have been even more amusing today. Actually, manual roll-down windows would give it extra charm — you just don’t see them anymore. I think I put it well in “A Decade with Accent” when I said: “If I needed personal transportation, I would buy an Accent again.” I still stand by that statement, even though I wish the Accent had a bit more comfort in the seats — I guess age is talking now. On the other hand, I’ve found that noise-cancelling headphones make any long trip in any car a lot more comfortable.

So what’s the plan for the future? The car is actually in pretty good shape. It has one rust spot on the body that I can’t seem to eradicate, and some minor rust on the subframe (which I treat every so often), but otherwise, it looks good. Don’t get me wrong — it has paint chips (highway driving) and dents (thanks to my mom and local stores), but hey, which car doesn’t at 300K kilometres?

So the plan is to drive it as long as I can and see how far I can take it. The engine is still good, no burning oil, even though it shakes somewhat noticeably on idle and throttle body cleaning didn’t help — but I’m sure I’ll figure it out eventually. The transmission is still shifting just as it did when new (which wasn’t smooth on second gear). No major issues to speak of, only a few small things like the light switch in the trunk that doesn’t seem to work anymore (I should replace it). So, let’s see if it can make it to 20 years — and perhaps beyond.

Amazon Fire TV Stick 4K Max

I got a bit upset with Apple TV over the remote and decided to switch away from Apple TV completely — vote with your dollars, as they say. I’ve seen Fire TV on multiple occasions and figured that for the discounted price of $50 Canadian dollars, I should give it a go.

Now, I really don’t need much — Netflix, Discovery, YouTube, and something that can play video files off my NAS. Fire TV has all of those apps, including a pretty nice Nova Video Player that isn’t perfect but sure isn’t far from Infuse (on Apple TV). After about a week of using Fire TV, I’m ready to pass my judgment.

Short and sweet: Fire TV performs pretty well. Cold start is fairly fast (same or faster than Apple TV), and warm start is almost instant. Apps seem to work well, the menu is smooth, apps start fast, and overall I don’t see any difference between the two in terms of performance.

The big difference comes from setup, ads, and probably privacy. Setup is different — Fire TV is hands-on. It must be configured for a particular TV, which involves following a setup wizard. I wish it was as brain-dead simple as Apple TV, but it isn’t complicated — just needs a bit of patience.

Once setup is done, the very noticeable difference comes in the form of ads. By default, Fire TV plays ads for Amazon shows, games, and other things. That comes down to personal taste — it doesn’t bother me. I don’t watch TV or shop much, so seeing ads for movies and TV shows is actually kind of nice; I get to see what’s new out there. However, I can see how ads could become really old, really fast. If you tinker a bit with settings you can disable ad autoplay (which I did), but you can’t get rid of ads completely. I’m sure you can block them via a firewall or by tinkering with the OS internals, but I haven’t explored that option yet.

Now, Amazon isn’t known for its strong user privacy stance, so in that department you get what you pay for. If privacy is at the top of your list, perhaps give it another thought. Otherwise, you’ve got yourself a smart TV experience at a very cheap price.

Overall, Fire TV feels solid and very competitive. I’d recommend it — unless you’re concerned about privacy, hate ads, or simply prefer to stay in the Apple ecosystem.

Goodbye Apple TV

I enjoy Apple products and I’ve got a few, including two Apple TVs. I’m not big on smart-TV stuff, so I only use it for Netflix, YouTube, and some “running around the world” workout videos I keep on my NAS. I know I don’t use Apple TV to its full capabilities—no games, no apps (beyond the above)—and that’s OK by me. I was happy to give Apple my money; their stuff never let me down, and I enjoyed the experience.

However, this year I was forced to reevaluate my choice. It started when the Apple TV remote battery began to die—it didn’t hold a charge and needed constant recharging. I figured it wasn’t a big deal; I could just change the battery and carry on. Then came the surprise: modern Apple remotes are sealed, and the battery is irreplaceable. I was shocked and disappointed. Maybe it was only that model, I thought; maybe the newer one is different. The new remote looked different—the trackpad is gone and it looks beefier—but it kept the same brain-twisting “feature”: an irreplaceable battery. Even if you’re ready to hack and solder, you can’t buy a replacement battery. Why?

In the old Apple remotes you could always replace the battery, but I guess in this new age of disposable stuff you should just buy a new one. OK, well, let’s buy a new remote… except it isn’t $30–$40; it’s a whopping $80 Canadian. Why? A new Apple TV is $180! So nearly half the cost of the Apple TV is the remote? Really? And the cherry on top: there are no third-party options that work decently.

Everyone knows TV remotes don’t get dropped, smudged, or soaked in breakfast cereal! So yeah, good on Apple for noticing this potential revenue stream and deciding to nickel-and-dime their customers. “Think Different” takes on a new meaning. I mean, it’s a TV remote! Just for comparison: for $80 Canadian you can buy the top model of the Amazon Fire TV Stick—and if you wait for a sale, you can get it down to $50. By the way, the Fire TV remote has the same features—buttons, microphone, Bluetooth—and somehow it all runs on two AAA replaceable batteries! I really didn’t mind giving Apple two or three times more money for Apple TV, but not for an unrepairable, disposable, expensive TV remote.

My journey with Apple TV is coming to an end. I’ve already replaced one, and I’m seriously considering replacing the other as well. Maybe it’s an overreaction, but I can’t help feeling cheated. There’s a lingering distaste I can’t seem to shake off — it just sits there, quietly dulling any reason to look back.

Death of Dream Router

After almost two years, my Dream Router is crashing my dreams—it’s dying. The signs are simple: it freezes and becomes totally unresponsive. A hard reboot (unplug/plug power) brings it back like nothing happened. After contacting tech support, it looks like I’ll be getting a replacement (version 7). They also told me there’s a hardware issue in version 6 of the Dream Router.

Okay, I have a bit of a rant to perform, but before I dive into my seemingly endless expression, full credit to UniFi’s tech support — it was fast, easy, and pleasant. Only one “but”: UniFi wants the old router back before sending a new unit. If you don’t have a spare, that’s a pickle (which I’m in—hopefully that gets worked out).

Now, back to the rant. Honestly, I don’t have many disappointments—just a major one: the Dream Router is not as stable as I expected. My router expectations are simple: configure it and enjoy life — stable Wi-Fi, stable network, and no reboots unless it updates or performs some kind of maintenance. The Dream Router is more than just a router with Wi-Fi; you can connect a bunch of UniFi devices to it, but I didn’t. I wanted a stable device first — this is a fairly expensive router — and I didn’t want to dive into the UniFi ecosystem until I was satisfied with the product quality. Unfortunately, I made the right call: the Dream Router lasted a little less than two years and, by the end, I was turning off features just to make sure it wasn’t overburdened.

My configuration is fairly simple: two Wi-Fi networks — main and guest — NAT to route HTTP/HTTPS traffic to my server, QoS to limit speed to that server, IDS (intrusion detection system), and the default firewall settings. Not a lot of tuning, to be honest, and yet the router needed constant soft reboots every 2–3 weeks. The pattern was always the same: Wi-Fi would get weird — the internet would stop for a few minutes, then come back. It would repeat and get worse over time. A soft reboot, and everything was fine again like nothing ever happened. The router was like that from the very beginning, which was disappointing. Then, in early 2025, it got a software update and turned into a champ — no soft reboots, no network issues, nothing. I couldn’t believe how stable it became. My happiness didn’t last past five months; after another software update, it reverted to the old behavior — 2–3 weeks of solid work, then network issues, then a soft reboot. Rinse and repeat.

I got tired of soft rebooting (I wish there were a setting in the UI to schedule reboots), so I wrote a small bash script to log in and soft reboot it every three days. I figured that would be the end of it. Two months later I was staring at a frozen router, and ever since, it’s been freezing more frequently — finally setting an all-time record of not even 24 hours before needing a hard reboot. I disabled QoS and IDS—none of it helped. You never know when it’ll freeze again; right now it doesn’t last more than six days.

What really upsets me is the price. At the $400 mark, I expected a quality device, but it behaves like any other $100 router of the past. Yes, the Dream Router is fancy—lights, an LCD, very nice software inside (with some drawbacks I mentioned in the first article), and integration with cameras, doorbells, and other UniFi devices — but it’s unstable. How can I rely on it when it fails as a simple router? Do you really want to trust it with door access?

Hopefully I’ll receive my replacement Dream Router soon and give it another go. However, when I have to buy another router, it’s not going to be the Dream Router. I think I’ll go back to DD-WRT or another open-source firmware router.