Not too sure whats going on here. Just fired it up for the first time and it crashes with this error
/usr/bin/python: No module named py_captions_for_channels.main; 'py_captions_for_channels' is a package and cannot be directly executed
Not too sure whats going on here. Just fired it up for the first time and it crashes with this error
/usr/bin/python: No module named py_captions_for_channels.main; 'py_captions_for_channels' is a package and cannot be directly executed
Which installation process did you use?
Docker/Portainer/Intel GPU
what operating system and version?
Lubuntu and I'm running the latest version
Found it — thanks for reporting this, it was a real bug. Not your setup.
The short version: the container's docker-compose file had a leftover developer setting that replaces the app's code inside the container with whatever's in the folder next to your compose file. For a normal git clone install that's harmless, but with Portainer that folder doesn't actually contain the source — so it wiped out the container's entry point and it crashed on startup with that error. Nothing you did wrong.
I just pushed a fix. Once the new image finishes building (check back in an hour or so), here's what to do:
docker compose pull && docker compose up -d if you're on the CLI)..env).Let me know if it still doesn't come up after redeploying and I'll dig further.
After it successfully installs, the next challenge will be to get it to work. I've tested CPU-only with Intel, but I have not fully tested parakeet or groq on an Intel GPU system yet. So, you're in beta territory. Here's the instructions for switching to parakeet after running the installation wizard (The wizard doesn't know about parakeet or groq yet):
Two equivalent ways — no container restart needed either way, since the transcription step runs as a fresh subprocess (python -m py_captions_for_channels.embed_captions) per job that re-reads .env from scratch. Whatever's set takes effect on the next job, not before.
Via the web UI:
Settings → find WHISPER_LOCAL_ENGINE → switch dropdown from faster-whisper to parakeet → Save. PARAKEET_DEVICE should already default to cpu (the only supported value right now).
Via .env directly (or the equivalent Portainer stack var if you went that route):
WHISPER_ENGINE=local
WHISPER_LOCAL_ENGINE=parakeet
PARAKEET_DEVICE=cpu
This only takes effect if WHISPER_ENGINE=local (or if it's on groq and it falls back to local on a failed cloud call)
Ok, thats a bit confusing
All of the environmental variables should be in the docker-compose file then. Having another .env file on top of the environmental variables that already has to be set is confusing AF and unnecessary. I think thats causing all the bugs here
Fixed and tested end-to-end — please give it another try. Redeploy from the current main/latest image; the crash you hit is gone. There's now a dedicated pre-flight script in INSTALL.md that validates your DVR URL and recordings path before deploying, so you shouldn't hit a repeat of the original issue. After it's up, run the in-app Setup Wizard (
gear icon) — it's a required step, not optional, see below. Let me know how it goes.
Neither of the local transcription engines, faster-whisper nor parakeet support the Intel GPU, so CPU is the only option for now. CTranslate2 (Whisper) only supports NVIDA, and the project doesn't have any plans for Intel support at this time. Parakeet supports it, but it doesn't work yet, so that may change soon.
Thumbnail sketch of the actual process:
docker-compose.yml into Portainer, load the config file as environment variables, deploy.
gear icon, web dashboard) — required, not optional. The pre-flight step deliberately doesn't know the mapping between how the DVR server reports a recording's path internally and where that file actually lives inside the container — those two paths are rarely identical. Without running the wizard, the app can start up fine and still fail to find any actual recording — it looks like it's working until you try to process something. The wizard queries the DVR API and auto-detects this mapping for you.If not using Portainer, use setup-linux.sh and continue the process above from step 3. This also works with Portainer but you don't get full Portainer control (no stack created).
You can try Groq after you set up local transcription. Get a free API key from them, put the key into settings and change the transcription source from local to cloud (Groq).
Actually, I might be able to get some Intel transcription support. I'm building and testing it now. Meanwhile, go ahead with the current image.
Thanks for this! I've been trying for a few days to figure out a way to add Whisper-detected captions after the fact, only to find out you already solved this months ago! I've run a test file, and the captions were noticeably better than what we had (real time ah4c transcription). Nothing against ah4c because it does a great job in real time, matching any live program I've ever seen with captions. It's just the majority of our Channels watching involves pre-recorded shows where the captioning nearly exactly matches the speech instead of lagging by 2-4 seconds.
I noticed what might be my first issue. Trying to add Guy's Grocery Games to the whitelist using the checkbox in Recordings has no effect. I'm assuming the apostrophe is throwing it off. I manually added it as Grocery Games since I think there won't be other matches.
I've also noticed .orig files continue to be created after deselecting the "Keep Original" option, saving and restarting the container. However, I have multiple jobs in progress and am waiting to see if they are auto deleted when all jobs are complete.
I'm looking forward to pairing this with my new ah4c setup from now on. Thanks again!
On some recordings, the audio language is set to "und" in the metadata. I have these settings in .env:
AUDIO_LANGUAGE=eng
# Subtitle language ("same", "none", or ISO code)
SUBTITLE_LANGUAGE=same
# Fallback strategy when language not found
LANGUAGE_FALLBACK=first
Even with this, the "und" is truncated to "un," and captioning fails with:
2026-10-06 12:14:24 [ERROR] pipeline.manual_process::/media/DVR/TV/The Rookie/The Rookie S01E05 2018-11-20 The Roundup 2026-10-06-1059.mpg::20261006-161314-864634: Faster-Whisper transcription failed: 'un' is not a valid language code (accepted language codes: af, am, ar, as, az, ba, be, bg, bn, bo, br, bs, ca, cs, cy, da, de, el, en, es, et, eu, fa, fi, fo, fr, gl, gu, ha, haw, he, hi, hr, ht, hu, hy, id, is, it, ja, jw, ka, kk, km, kn, ko, la, lb, ln, lo, lt, lv, mg, mi, mk, ml, mn, mr, ms, mt, my, ne, nl, nn, no, oc, pa, pl, ps, pt, ro, ru, sa, sd, si, sk, sl, sn, so, sq, sr, su, sv, sw, ta, te, tg, th, tk, tl, tr, tt, uk, ur, uz, vi, yi, yo, zh, yue)
Is there a way in Py Captions to force a language when "und" is detected? For now AI helped me figure out how to change it to eng with ffmpeg:
target="The Rookie S01E01 2018-10-16 Pilot 2026-10-01-1259.mpg"
tmp="The Rookie S01E01 2018-10-16 Pilot 2026-10-01-1259.tmp.mpg"
if ffmpeg -i "$target" -map 0:v -map 0:a -c copy -metadata:s:a:0 language=eng -f mpegts "$tmp"; then
mv -f "$tmp" "$target"
echo "Metadata successfully updated."
else
echo "FFmpeg failed; keeping original."
rm -f "$tmp"
fi
I'd prefer to either force ah4c to set "eng" when "und" is detected or have Py Captions do the same. It doesn't even need to change metadata like I am. If it can just change "und" to "eng" (or the pre-defined AUDIO_LANGUAGE) when sending the command to Faster-Whisper, I think that would suffice.
Thanks!
Thanks for the detailed report — all three were real bugs, and they're fixed:
"und" audio language: Py Captions was cutting the language tag down to two letters, so und became un. It now converts the tag properly to the code Whisper expects. When the tag is missing or und, it uses your AUDIO_LANGUAGE setting, so with your .env those recordings will be transcribed as English. You won't need the ffmpeg metadata workaround anymore.
.orig files with Keep Original turned off: The setting was saved but never actually used. Now, with it off, the .cc4chan.orig backup is deleted as soon as the captioned file has successfully replaced the recording. .orig files that already exist won't be deleted automatically, so you'll need to remove those by hand. Also note that without the backup, reprocessing a recording will start from the already-captioned file.
Whitelist checkbox and apostrophes: You guessed right — the apostrophe in "Guy's Grocery Games" broke the checkbox. That's fixed. Titles with characters like ., ? or parentheses are now also saved so that they match exactly. Your manual "Grocery Games" entry will keep working, so you don't need to change it.
Glad the captions are working well with your ah4c setup!
The whitelist processor may still have some issues because I have noticed that sometimes a program that I expected to be processed wasn't. That may be the issues you found, or it might be related to the time constraints not working properly. TBD...
Awesome, thanks for the quick response! I wound up transcribing everything I've recorded since I setup ah4c, but it will be nice to have those in place going forward! It also helped me find multiple programs where I recorded enough things in a row to trigger the "are you still watching" from Xfinity. Since I didn't have ah4c setup properly, I wound up with a few hours of the Xfinity guide. So riveting!
Thanks again!
One minor oddity I've noticed is that the captions will display the next upcoming caption long before the words are spoken if there's a good bit of silence. For example, a show goes to commercial with "We'll be right back after these messages" and then the caption reads "Have you ever been in a car accident?" for 10-20 seconds until the screen fades to black, the commercial starts, and the announcer finally says those words. The captions will be in sync until the next period of silence, such as "Welcome back to the show" appearing immediately after the last commercial's final words and remaining until the show actually starts up and the host says that phrase.
It doesn't always happen, but it's frequent enough that I figured I'd point it out in case there's any way to fix it.
Let me know if you'd rather I leave comments/questions in the repo or here. One question I have is if there's any way to run Py Captions against two CDVR instances. I'm in the process of setting my mom up with her own CDVR that uses our server as the m3u and EPG. (She lives in a detached apartment on our She can watch live or start a recording using our tuners with ah4c, but Py Captions won't process the files because they're on a different server. I thought about running another container with her CDVR as the target, but I think that would overload the GPU if both containers were trying to process a recording at the same time. It would be great if somehow I could add both servers to the one Py Captions instance, allowing it to internally sequence caption detection one at a time no matter which server hosts the recording.
I made some updates addressing requests by @tanderson92 as well as a couple of my own. The multi-server support is not tested yet. I'll get around to that soon, but it's included if you want to beta test for me
That's something that I considered a while ago because I'm running two DVR servers, one with a decent GPU and the other not. I solved that problem by pointing the second server to the first one as imported content. That works for some recordings, but not for anything only available on the second server. This solution should do the job. It requires that you run all instances of the captioning program on the server with the GPU. Here's the full description of the updates:
py-captions-for-channels update: caption timing fixes and multi-DVR GPU sharing
Caption fixes
Several Channels DVRs, one GPU
If you have more than one Channels DVR but only one machine with a good GPU, you can now run one instance per DVR on that machine. Each instance has its own settings and web UI, and they take turns on the GPU automatically.
Also fixed: a single instance could sometimes run two jobs at once, for example a manual reprocess alongside an automatic job, which split the GPU and slowed both down. Jobs now run one at a time.
Upgrading
git pull, then docker compose pull && docker compose up -d in your install folder.docker-compose.yml, then update the stack with Re-pull image turned on.Recordings that were already captioned keep their old timing until you reprocess them. If you kept the originals, you can reprocess from the web UI.
Wow fantastic update, thanks again! I setup my second instance and have been putting it through its paces. I tried to have manual and scheduled recordings on both servers, and so far it's been taking turns without any GPU OOM or other errors. I haven't had time to actually watch anything yet because I'm out of town another few days, but when I get back I'm looking forward to seeing the new caption timings.
This has really been a game changer. Thanks again for your hard work.