Will do. I'm recording one with a 2-second delay right now. If that passes the wife test I won't need to look any further.
If you have the HW for it have you tried turning on realtime? Several models support it. Just uses more memory and GPU for processing. It's usually fine if you don't have 10 tuners like yours truly.
oh @tanderson92 unless you have CUDA backing and a nice GPU I'd avoid Cohere. It's a great model but not great for closed captions unless you really have fast graphics to run it.
I have an iGPU and NVIDIA card on my Proxmox server. The NVIDIA card is passed to one of my VMs and the iGPU to my media VM because it was recommended for Plex/Jellyfin transcoding. I have ah4c on the media (iGPU) VM right now. According to ah4c it ran Cohere 4 second delay at 4x or higher; it's currently running Cohere 2 second delay at 2.9x. I'll try Parakeet real time and Nemotron real time to see if those are any better if Cohere 2 seconds doesn't work for us.
How much memory do you have.
Proxmox is a subject that's very near-and-dear to my heart. I run almost nothing bare metal anymore, everything is virtualized on Proxmox, and it works incredibly well for me. There's really nothing better for the tinkerer. 
One suggestion I would make. VMs are great for something like Windows, but LXCs are the answer for just about everything else. It keeps virtualizations lightweight, and allows for the sharing of resources like an iGPU across those virtualizations.
In fact, Channels DVR itself makes an excellent LXC:
Then you can run Docker in another LXC, with ah4c and other projects. Moving LXCs like this from one Proxmox host to another is super easy with a Proxmox Backup Server, to balance loads or to take a host down for an upgrade or other maintenance.
64GB
I'm still new at Proxmox and built my system based on the best recommendations I could find during my preparations. I have a dedicated Channels VM and a separate Media VM that has Plex, Jellyfin, ah4c and anything else I want to access the iGPU while also seamlessly accessing each other.
I'm looking at spinning up a 2nd CDVR server to be my Mom's (using the main CDVR as the channel source) because she seemed surprisingly open to switching how she watches TV. If I do, I'll make that one an LXC and see how I like it. If that works maybe I'll spin up an LXC for my main server and migrate the settings there. One thing at a time, as she absolutely requires stable captions as opposed to my wife who just prefers watching that way.
I have a very fast (4-5 second wake to playback) set of Osprey deeplinks scripts to PR but at the same time I wonder if any bugs have been fixed with com.att.tv and I want to see if any lessons I learned from debugging my Ospreys can apply to the native client. The FC bridge is good but having more then one option is ideal and I see the current scripts/firetv/directv are going on 3 years without a refactor.
I also plan on updating the channel# Osprey scripts a bit also. The readiness gate is a win for reliability on those but the heartbeat is 100 percent no longer needed. They fixed that in com.att.tv.openvideo with a recent patch. That Japanese IME key code I found however is perfect to prevent keep watching prompts so those just need a slight tweak!
Those are the old remote control emulation scripts we used before the deeplinks were uncovered. There's another set of scripts that use deeplinks.
I see. That makes more sense. I wanted to poke around with them just to see if maybe I know there were some issues with deep linking into them, especially if you do am kill—that will like produce a failed deep link pretty easily. I want to see it maybe my readiness gate that I developed for the Osprey scripts could work around that. That's what I was going to experiment with.
Experiment away! We have two relevant sets of scripts firetv/dtvdeeplinks and firetv/dtvstreamdeeplinks. We needed two back before the "merge" of DTV and DTV Stream. Not really necessary now. I'd suggest working with firetv/dtvstreamdeeplinks as a starting point.
On a grander scale, I think a number of scripts could transition from device specific to a device of all. The differences are so minimal anymore that I believe we could move most to all/<provider>, since other than package name, most scripts are close to identical.
I think I got something solid. Going home then running am kill com.att.tv would usually throw it into a bad state. I managed to sort that. On the warm path I'm seeing 5 second or so tunes!!
prebmitune
#!/bin/bash
# prebmitune.sh for osprey/dtvospreydeeplinks
# 2026.09.12
#Debug on if uncommented
set -x
streamerIP="$1"
streamerNoPort="${streamerIP%%:*}"
adbTarget="timeout 15 adb -s $streamerIP"
mkdir -p "$streamerNoPort"
#Trap end of script run
finish() {
echo "prebmitune.sh is exiting for $streamerIP with exit code $?"
}
trap finish EXIT
adbWake() {
$adbTarget shell input keyevent KEYCODE_WAKEUP
}
adbConnect() {
adb connect "$streamerIP"
if adbWake; then
echo "Waking $streamerIP" > /proc/1/fd/1
touch "$streamerNoPort/adbAppRunning"
return 0
fi
touch "$streamerNoPort/adbCommunicationFail"
echo "Communication with $streamerIP failed" > /proc/1/fd/1
exit 2
}
main() {
adbConnect
}
main
bmitune
#!/bin/bash
# bmitune.sh for firetv/dtvstreamdeeplinks
# 2026.09.30
#Debug on if uncommented
set -x
#Global
channelID=$(echo $1 | awk -F~ '{print $2}')
channelName=$(echo $1 | awk -F~ '{print $1}')
streamerIP="$2"
streamerNoPort="${streamerIP%%:*}"
adbTarget="adb -s $streamerIP"
keepWatchingInterval="$KEEP_WATCHING"
keepWatchingKeycode=KEYCODE_ZENKAKU_HANKAKU
mkdir -p $streamerNoPort
echo $$ > "$streamerNoPort/bmitune_pid"
#Trap end of script run
finish() {
echo "bmitune.sh is exiting for $streamerIP with exit code $?"
}
trap finish EXIT
tuneChannel() {
$adbTarget shell "
if pidof com.att.tv >/dev/null; then
am start -a android.intent.action.VIEW -d 'dtvnow://deeplink.directvnow.com/play/channel/$channelName/$channelID' com.att.tv
else
am start --activity-clear-task -a android.intent.action.VIEW -d 'dtvnow://deeplink.directvnow.com/play/channel/$channelName/$channelID' com.att.tv
fi"
}
startKeepWatching() {
if [[ -f "$streamerNoPort/keepwatching_pid" ]]; then
kill -- -"$(<"$streamerNoPort/keepwatching_pid")" 2>/dev/null
fi
cat > ./$streamerNoPort/keep_watching.sh <<HBEOF
#!/bin/bash
trap 'echo "keep watching pid killed" > /proc/1/fd/1; exit 0' TERM INT
echo "keep watching started for $streamerIP -- keyevent $keepWatchingKeycode every ${keepWatchingInterval}s" > /proc/1/fd/1
while true; do
sleep $keepWatchingInterval
$adbTarget shell input keyevent $keepWatchingKeycode
done
HBEOF
chmod +x ./$streamerNoPort/keep_watching.sh
setsid ./$streamerNoPort/keep_watching.sh >/dev/null 2>&1 &
echo $! > "$streamerNoPort/keepwatching_pid"
}
main() {
tuneChannel
if [[ -n "$keepWatchingInterval" && "$keepWatchingInterval" != 0 ]]; then
startKeepWatching
fi
}
main
stopbmitune
#!/bin/bash
# stopbmitune.sh for firetv/dtvstreamdeeplinks
# 2026.09.29
#Debug on if uncommented
set -x
streamerIP="$1"
streamerNoPort="${streamerIP%%:*}"
adbTarget="adb -s $streamerIP"
#Check if bmitune.sh is done running, then kill the heartbeat before the device is slept
bmituneDone() {
bmitunePID=$(<"$streamerNoPort/bmitune_pid")
while ps -p $bmitunePID > /dev/null; do
echo "Waiting for bmitune.sh to complete..."
sleep 2
done
if [[ -f "$streamerNoPort/heartbeat_pid" ]]; then
heartbeatPID=$(<"$streamerNoPort/heartbeat_pid")
kill -- -"$heartbeatPID" 2>/dev/null
rm -f "./$streamerNoPort/heartbeat_pid"
fi
rm -f "./$streamerNoPort/heartbeat.sh"
}
#Stop DTV's playback so there's nothing to reload on the next wake, then sleep
adbSleep() {
$adbTarget shell "input keyevent KEYCODE_MEDIA_STOP; input keyevent KEYCODE_SLEEP"
echo "Sleep initiated for $streamerIP"
date +%s > $streamerNoPort/stream_stopped
echo "$streamerNoPort/stream_stopped written with epoch stop time"
}
main() {
bmituneDone
adbSleep
}
main
Looking for some testing and feedback before I open a PR. Please try and break them! This works on firetv and google tv.
Limited testing so far, but it does look good! It still takes a bit to load the app cold, but the warm tunes are nice and quick. I put the scripts in scripts/all/dtvstreamdeeplinks (and adjusted the comment block accordingly) on my local system, and I think that's where we should put these when merged.
Cool, I'd just like more people to try and break them then I think we have something solid. My goal was to account for cold/broken tunes. While also fast channel flipping.
Ive been playing with the adb ah4c now this morning with two android devices in the same container for the first time. I ran into more LinkPi issues, but I'll report that in the linkPi thread.
Now that Ive been playing around with this, is it conceivable to have different scripts assigned on a per tuner basis (in the same container)? At the moment Im not using this for DTV. Ive got a use case where one of my tuners is running paramount +, and Id like my second tuner to run the channel 4 UK app on a different stick
Instead of firing up a third container, which Im considering, I thought Id put this out there....
How about , as long as one is using adb control (not the PYATV) in their container, when a new tuner is added in the ah4c UI, you have the ability to load a different bmitune, prebmitune, and stopbmitune for each tuner as they are added (make this optional)
This way one of the tuners can have different VPNs set up to different locations
Then the tuning scripts wouldn't have to take the extra time and overhead to manage all that stuff for each tuning sequence. In my experience it can save 10s of seconds of tuning time.....
Not quite the way you're requesting. As I read your question, you're basically asking (again) for the tuner pool to not be interchangeable. This is not the way ah4c is written.
We do, however, have the ability now to run different sets scripts based on the provider. In other words, the tuner pool is still interchangeable -- but, the scripts can vary. This is done by creating an all.m3u, from two or more other M3Us, using the WebUI.
EDIT: I will amend this slightly, to say that it is possible to specify a single tuner to use in a tuning URL. This is done by adding the tuner number, like so: play/tuner0/ or play/tuner1/. If you're only looking to pin one tuner, and all tuning URLs in the M3U you are using specify either one tuner or the other (assuming two virtual tuners), this could work.
There are obvious downsides to this approach, but it is a possible method. You'd start by creating an all.m3u, and then editing it to assign a tuner to each tuning URL.
Yes - this is the way
I got hung up thinking that the limitation in the ah4c architecture with the tuner pool was due to the differences between PYATV and ADB flavors of containers and asked again for script per tuner granularity.
Now that Im in the (every tuner in this container is android based) - if I can pass the actual device from the all.M3U that looks great . I'll play around with this approach. Thank you!!
I forgot to re-edit the script after updating and lost my first recording that was on "E! HD" due to the space. Can the double- and single-quotes be added to the xfinity script permanently if you don't think it will break anything else @bnhf? Thanks!
Done. Pushed as bnhf/ah4c:latest (aka bnhf/ah4c:2026.09.30).