Is there a way to automatically convert DVR files after recording in Plex?
When watching DVR content from OTA antenna (ts file) on Roku, Plex is transcoding and maxing out CPU usage causing horrible stutters at random points. I’ve found using Handbrake to convert the ts file to mp4 not only shrinks the size considerably (3.28GB to 916MB for a one-hour show minus commercials), but allows for direct play on my Roku without the skips, and keeps CPU usage under 10-15%.
I’m trying to find a way to automate the process, replacing the original (if it’s just a show I want to watch and delete) with the file Handbrake puts out. I do see a “Postprocessing Script” option in Plex DVR settings, maybe that’s it–can someone help me with that?
What tuner are you using? If it is a HDHR then there is an option in Plex for the device to have Plex convert while recording. See this article and scroll down to the “Other HDHomeRun Devices” section for how to set this up. If you happen to have a HDHR Extend then then tuner itself is capable of converting.
I see the ‘transcode’ option but says it requires a fast CPU and I’m afraid since the CPU couldn’t handle transcoding on the fly, if I start that will mess with the original recording that will make the situation worse
My script wasn’t working after updating to Ubuntu 20.04 and latest PMS, I THINK I’ve debugged it though. I’m about to put it through it’s paces. Once I verify that it works, I’ll share the updated version.
My script does the following: auto-crops, auto maximizes volume level, re-matrix audio to DPLII, converts subtitles, encodes using Intel VAAPI hardware transcoding, then moves the original .ts file to another location. It also includes joe queueing functions and can change encode parameters based on the vertical resolution of the file, or the name of the show.
I’ll re-share once I’ve verified it’s all working again.
Script is working fine now. New change in 20.04 requires the plex system user to be added to the “render” group to get hardware acceleration working.
This script is written for use on Linux using VAAPI acceleration for Intel graphics chipsets, and it depends on having ffmpeg installed. I’ll post up the code ASAP.
My next goal for this script is to get it working using Nvidia acceleration.
It’s possible this could be adjusted to work on Mac and Windows as well, but I don’t think I’ll be working on that anytime soon.
Here’s my script, working on Ubuntu 20.04 using Intel VAAPI hardware decoding and encoding. Auto-crop, auto volume boot, transcode options based on size, variables to custom-process certain shows, job queueing and logging. I can’t take credit for a lot of this, as I cobbled things together from a variety of sources that were way more knowledgable than I.
#!/bin/bash
#
# Plex post-processing script
#
# Job Monitoring Variables
[ -d /var/tmp/plexpost ] || mkdir /var/tmp/plexpost
[ -d /var/tmp/plexpost/jobs ] || mkdir /var/tmp/plexpost/jobs
job_id=$$
job_wait_timer=5
plex_tickets="/var/tmp/plexpost/plex_tickets.txt"
results_file="/var/tmp/plexpost/jobs/plex-results-"${job_id}".txt"
>$results_file
echo "${job_id}" >>$results_file
# Setup File Names
filename="$(basename "$1")"
outfile="${1//.ts/.mkv}"
subfile="/var/tmp/plexpost/jobs/${job_id}".srt
# create symlink to avoid probelms with apostrophes in file names
temp_link="/var/tmp/plexpost/jobs/${job_id}".ts
/bin/ln -s "$1" "$temp_link"
#directory to which TS file is moved after encode
tsout="/media/750/PlexTS/"
# FFMPEG Parameters
hwi="-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi"
filter="-vf "deinterlace_vaapi=rate=field:auto=1""
hwo="-vaapi_device /dev/dri/renderD128"
vc="-c:v h264_vaapi"
qp="-qp 27"
rate="-r 30000/1001"
ac="-c:a aac -b:a 96k -ac 2"
subs="-f srt -i"
echo "$@" >>$results_file
echo "$1" >> /var/tmp/plexpost/plex_post_processing_files.txt
# Discover Input Stream Properties
echo "Reading Input Stream Properies..." >>$results_file
video=$("/usr/bin/ffprobe" -hide_banner -i "$1" 2>&1 | egrep " Video: ")
format=$(echo $video | sed 's#.* \([[:digit:]]\+x[[:digit:]]\+ [SAR [[:digit:]]\+:[[:digit:]]\+ DAR [[:digit:]]\+:[[:digit:]]\+]\).*#\1#')
height=$(echo $format | sed "s#[[:digit:]]\+x\([[:digit:]]\+\) .*#\1#")
# routine to queue jobs
queue_job () {
echo "Queueing job: $job_id" >>$results_file
if [ ! -f "${plex_tickets}" ] ; then
echo $job_id >>"${plex_tickets}"
#set initial mode of file
chmod 666 "${plex_tickets}" >>$results_file 2>&1
else
echo $job_id >>"${plex_tickets}"
fi
current_job=$(head -1 "${plex_tickets}")
while [ $job_id -ne $current_job ] ; do
depth=0
echo "Job: ${job_id} is not ready. Job: ${current_job} is running" >>$results_file
while IFS= read -r ; do
ticket=$REPLY
let depth++
if [ ${job_id} -eq ${ticket} ] ; then
position=${depth}
#break
fi
done < "${plex_tickets}"
#depth=$(wc -l $plex_tickets | awk '{print $1}')
echo "Job: ${job_id} queue position ${position} of ${depth}." >>$results_file
echo "Sleeping ${job_wait_timer} seconds" >>$results_file
sleep ${job_wait_timer}
current_job=$(head -1 "${plex_tickets}")
done
echo "Job: ${job_id} is ready!" >>$results_file
}
# routine to remove jobs
remove_job () {
echo "Removing job: ${job_id} from ${plex_tickets}" >>$results_file
# this step actually creates a new file with new ownerships, but maintains previous mode
sed -i "/^$job_id$/d" "${plex_tickets}"
}
# Get in line
queue_job
#Detect crop
echo "Detecting Crop..." >>$results_file
crop=$(/usr/bin/ffmpeg -i "$1" -t 300 -vf cropdetect=25:16:1800 -f null - 2>&1 | awk '/crop/ { print $NF }' | tail -1)
echo $crop >>$results_file
#Detect Audio Level
echo "Detecting Audio Level..." >>$results_file
amplify=$(/usr/bin/ffmpeg -i "$1" -af "volumedetect" -vn -sn -dn -f null - 2>&1 | awk '/max_volume/ { print $NF } /dB/' | tail -c 8 | sed 's/...$//' | sed 's/^.//')
echo "Amplify By ${amplify} dB" >>$results_file
af="-af volume=${amplify}dB,aresample="matrix_encoding=dplii""
## Set parameters based on input stream
#
# 1080 - Deinterlace, autocrop, and scale
if [ $height -eq 1080 ] ; then
nocrop="crop=1920:1080|crop=1920:1078|crop=1920:1076|crop=1920:1074|crop=1920:1072|crop=1920:1070"
if echo $crop | egrep "^(${nocrop})" >/dev/null 2>&1 ; then
filter="-vf "deinterlace_vaapi=rate=field:auto=1,scale_vaapi=w=1280:h=720""
else
hwi=""
filter="-vf "yadif,"$crop",scale=1280:-2,format=nv12,hwupload""
fi
fi
# 720 - Deinterlace and autocrop
if [ $height -eq 720 ] ; then
nocrop="crop=1280:720|crop=1280:718|crop=1280:716|crop=1280:714|crop=1280:712|crop=1280:710"
if echo $crop | egrep "^(${nocrop})" >/dev/null 2>&1 ; then
filter="-vf "deinterlace_vaapi=rate=field:auto=1""
else
hwi=""
filter="-vf yadif,"$crop",format=nv12,hwupload"
fi
fi
# 480 - Lower QP, Deinterlace, Autocrop
if [ $height -eq 480 ] ; then
qp="-qp 26"
nocrop="crop=704:480|crop=720:480|crop=720:478|crop=704:478|crop=720:476|crop=704:476|crop=720:474|crop=704:474|crop=720:472|crop=704:472"
if echo $crop | egrep "^(${nocrop})" >/dev/null 2>&1 ; then
filter="-vf "deinterlace_vaapi=rate=field:auto=1""
else
hwi=""
filter="-vf "yadif,"$crop",format=nv12,hwupload""
fi
fi
# 540 - Lower QP, Deinterlace, Autocrop
if [ $height -eq 540 ] ; then
qp="-qp 26"
nocrop="crop=704:540|crop=720:540|crop=720:528|crop=704:528"
if echo $crop | egrep "^(${nocrop})" >/dev/null 2>&1 ; then
filter="-vf "deinterlace_vaapi=rate=field:auto=1""
else
hwi=""
filter="-vf "yadif,"$crop",format=nv12,hwupload""
fi
fi
# Original Quality
shows="The Shivering Truth|Joe Pera|Show Name 3|Show Name 4|Etc Etc Etc"
if echo $filename | egrep "^(${shows})" >/dev/null 2>&1 ; then
filter=""
rate=""
vc="-map 0:0 -c:v copy"
qp=""
ac="-map 0:1 -c:a copy"
fi
# Better Quality
shows="Nature|Attenborough|NOVA"
if echo $filename | egrep "^(${shows})" >/dev/null 2>&1 ; then
qp="-qp 25"
ac="-c:a aac -b:a 192k -af aresample="matrix_encoding=dplii" -ac 2"
fi
# SNL
shows="Saturday Night Live"
if echo $filename | egrep "^(${shows})" >/dev/null 2>&1 ; then
qp="-qp 29"
fi
# Better audio for music programs
shows="Austin City Limits|70s Soul"
if echo $filename | egrep "^(${shows})" >/dev/null 2>&1 ; then
ac="-c:a aac -b:a 256k -af aresample="matrix_encoding=dplii" -ac 2"
fi
# Encode routine
echo "Starting post-processing..." >>$results_file
echo "Converting closed captions..." >>$results_file
/usr/bin/ffmpeg -hide_banner -y -f lavfi -i "movie='$temp_link'[out0+subcc]" -map s "$subfile" >>$results_file 2>&1
echo "Setting subtitle position..." >>$results_file
sed -i 's/an7/an2/g' "$subfile" >>$results_file 2>&1
echo "Encoding with subtitle track..." >>$results_file
LIBVA_DRIVERS_PATH=/usr/lib/x86_64-linux-gnu/dri/
/usr/bin/ffmpeg -hide_banner -y $hwi $hwo -i "$temp_link" $subs "$subfile" ${filter} $vc $qp $rate ${af} $ac -c:s copy "$outfile" >>$results_file 2>&1
echo "Cleaning up..." >>$results_file
rm "$temp_link" >>$results_file 2>&1
rm "$subfile" >>$results_file 2>&1
#Delete original file
#rm "$1" >>$results_file 2>&1
#Move Original File To Temporary Storage Instead
mv "$1" "$tsout" >>$results_file 2>&1
remove_job
echo "Complete." >>$results_file
Read through, see how it works, and make adjustments to suit your environment.