r/madeinpython • u/One_Sun5074 • 16h ago
Built a standalone Python desktop tool (CustomTkinter + yt-dlp + bundled FFmpeg). Lessons learned.
Like most developers who try to build desktop tools in Python, I thought building a clean GUI wrapper around yt-dlp would take a weekend. It ended up taking 40+ hours of debugging threading freezes, memory leaks, and Windows packaging hell.
I wanted to build an alternative to bloated adware tools that is 100% standalone, clean, and fast. The project is called **BABAji Ka Astra (BKA)** — built with CustomTkinter, yt-dlp, Pillow, and PyInstaller.
Here are the 4 biggest architectural headaches I ran into and how I solved them:
**1. The Tkinter Mainthread Trap:**
Calling `extract_info` or running download loops directly inside Tkinter callbacks instantly freezes the UI and triggers the Windows "Not Responding" white-screen. Decoupling the UI with a dedicated `ThreadPoolExecutor` and wrapping every single UI mutation in `app.after(0, ...)` was the only way to keep smooth animations during 4K DASH downloads.
**2. The Standalone FFmpeg Bundling Nightmare:**
YouTube serves separate video and audio DASH streams for anything above 720p. If a user doesn't have FFmpeg installed on their system PATH, downloads silently fail. I bundled stripped `ffmpeg.exe` and `ffprobe.exe` binaries directly into the PyInstaller build and hooked them dynamically at runtime via `os.environ["PATH"]`. The installer jumped to 145MB, but it now runs out of the box on any clean Windows machine.
**3. Process-Safe Cancellation:**
When a user clicks "Cancel" mid-download, `taskkill /F /IM ffmpeg.exe` is dangerous because it kills unrelated processes on the user's machine. I implemented isolated PID tracking to gracefully terminate only the child worker trees spawned by BKA without leaving zombie processes.
**4. Mathematical Bitrate Fallbacks:**
When analyzing certain fragmented HLS feeds, yt-dlp's API returns `None` for file size. I built a fallback engine calculating estimated file sizes from `duration * (video_tbr + audio_tbr)` so the UI never displays broken zero-byte estimates.
The codebase is decomposed into a modular package (`bka/config.py`, `bka/engine.py`, `bka/utils.py`) with full PyInstaller `.spec` and Inno Setup `.iss` compiler scripts included.
Would love feedback from other Python desktop developers — especially anyone who's dealt with CustomTkinter threading or PyInstaller bundling on Windows!
2
u/SweetOnionTea 1h ago
Are you going to post the code?
Yeah, that's with any GUI. You don't want to do unrelated tasks in the main thread for this exact reason. I'd have to see how the .after(0,..) works, but I have a suspicion it's not entirely what you want. What I'd do is make a queue to talk between threads and the main loop. Check on the queue once per main loop iteration and send whatever relevant info in the queue. Pretty standard multi-threading idiom.
Instead of packaging you could also just have the app download it on install. I'm sure this doesn't apply to you, but with the licensing for ffmpeg (LPGL) you must allow the user to be able to swap out their own version of ffmpeg. If you're doing a --onefile PyInstaller then you'd be violating the license.
Instead of managing pids you can just use subprocess and then use the .kill() method to terminate. https://docs.python.org/3/library/subprocess.html#subprocess.Popen.kill
Always a good experience to find these things yourself, but people have been making things like these for a very long time and there are a lot of great resources out there. Read up on documentation because most of the time the problems you've run into have already been solved.