Last active
January 6, 2026 15:51
-
-
Save rubikcuber/c1567c912e0fe0f215263689b1eb14e7 to your computer and use it in GitHub Desktop.
Spawn script for tvheadend to transcode live TV in real-time using an Intel / vaapi hardware device. Used
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # Spawn script for tvheadend to transcode live TV in real-time using an Intel / vaapi hardware device. | |
| # Create a new Profile in tvheadend called "pass", disable the built-in pass profile, and set this one as | |
| # the default. | |
| # | |
| # This is specifically designed for UK DVB-T and DVB-S processing for Plex DVR. The script transcodes | |
| # the incoming TV stream to 50hz video using field based deinterlacing. Plex enforces frame based | |
| # deinterlacing at all times, so if you have a client that cannot direct play recordings reliably | |
| # (e.g. Samsung and probably others) you will only be able to get 25hz "filmic" recordings. This | |
| # script deals with it at the source, so Plex never gets the interlaced video. | |
| # | |
| # The resulting MPEG-TS stream has errors and discontinuities fixed so TVs can Direct Stream reliably. | |
| log_dir="/config/logs" | |
| # Set RAW_CAPTURE_DIR to empty to disable raw capture. | |
| RAW_CAPTURE_DIR="/recordings/raw_streams" | |
| mkdir -p "${log_dir}" | |
| logfile="${log_dir}/stream-stable.$(date +%Y%m%d).log" | |
| ffmpeg=/usr/bin/ffmpeg | |
| hwdevice=$(ls -l /sys/class/drm/renderD*/device/driver 2>/dev/null | grep i915 | sed 's/.*\(renderD[0-9]*\).*/\/dev\/dri\/\1/' | head -1) | |
| if [ -z "${hwdevice}" ]; then | |
| hwdevice="/dev/dri/renderD128" | |
| fi | |
| echo "$(date): starting stable stream transcode" >> "${logfile}" | |
| echo "$(date): hwdevice=${hwdevice}" >> "${logfile}" | |
| if [ -n "${RAW_CAPTURE_DIR}" ]; then | |
| mkdir -p "${RAW_CAPTURE_DIR}" | |
| raw_file="${RAW_CAPTURE_DIR}/stream-$(date +%Y%m%d-%H%M%S).ts" | |
| echo "$(date): raw capture enabled at ${raw_file}" >> "${logfile}" | |
| fi | |
| # Always field-based deinterlace and 50fps output for stability. | |
| cmd=( | |
| "${ffmpeg}" | |
| -hide_banner -nostdin | |
| -fflags +genpts+discardcorrupt | |
| -err_detect ignore_err | |
| -avoid_negative_ts make_zero | |
| -max_interleave_delta 0 | |
| -analyzeduration 2M -probesize 2M | |
| -hwaccel vaapi -hwaccel_device "${hwdevice}" -hwaccel_output_format vaapi | |
| -i pipe:0 | |
| ) | |
| cmd+=( | |
| -map 0:v:0 -map 0:a:0 -map 0:s? | |
| -vf "deinterlace_vaapi=rate=field:auto=1" | |
| -r 50 | |
| -c:v h264_vaapi -b:v 6000k -maxrate 8000k -bufsize 12000k | |
| -c:a aac -b:a 320k -ac 2 | |
| -c:s copy | |
| -muxdelay 0 -muxpreload 0 | |
| -f mpegts -mpegts_flags +resend_headers+initial_discontinuity | |
| pipe:1 | |
| ) | |
| if [ -n "${RAW_CAPTURE_DIR}" ]; then | |
| tee "${raw_file}" | "${cmd[@]}" 2>> "${logfile}" | |
| else | |
| "${cmd[@]}" 2>> "${logfile}" | |
| fi | |
| exit_code=$? | |
| echo "$(date): ffmpeg exited with code ${exit_code}" >> "${logfile}" | |
| # If ffmpeg fails, fall back to passthrough to keep the stream alive. | |
| if [ "${exit_code}" -ne 0 ]; then | |
| echo "$(date): fallback to passthrough" >> "${logfile}" | |
| tee | |
| fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment