Server Version#: 1.19.2.2737
Player Version#: 4.32.2
The Plex (Pass) DVR allows for recording of OTA broadcasts via various tuner devices. In my experience (in NA), these devices simply capture the ATSC MPEG2 stream and drop it on the disk in a TS file format (some tuner devices may support their own real-time transcoding – not mine). MPEG2/TS is a pretty fat format and takes up a good amount of disk space (11 Mbps for some ATSC channels).
Goal: Upon recording completion, automatically convert the MPEG2/TS file(s) to a more efficient format (h264/MKV in this case) and remove the original TS file(s).
Plex’s built-in “optimize” feature does half the job. It will convert files once they are added to the library. But it is only additive. It does not have an option for deleting the original.
POSTPROCESS in the DVR settings allows for a user-provided script to run as the last step prior to the file(s) being added to the library. This seems like a good place to do some work.
Solution: Utilize the same command that “optimize” uses to convert the captured MPEG2/TS to an h264/MKV. As part of the POSTPROCESS script, we can then remove the original after transcode is complete.
NVENC acceleration note: The script below uses NVENC (nvidia) acceleration – just like “optimize” does in a supported environment. In my configuration (GTX 970, Intel Core i7 930), FFMPEG encodes at 500+ fps via NVENC (on the GPU) vs. 70+ fps without acceleration (on the CPU). Any quality offset due to h264_nvenc is worth the CPU time saved (opinion).
Script:
Limitations: No additional software may be installed on the Plex server (docker container in this case). FFPROBE is not available.
(I’m no BASH expert. I welcome any advice for making this script more robust.)
#!/bin/bash
# These PATHS are in the Plex Transcoder process environment when it runs for "Optimization"
# The FFMPEG library path looks like it could change between releases
# so we determine it based on the mpeg2video library location.
export FFMPEG_EXTERNAL_LIBS="$(find ~/Library/Application\ Support/Plex\ Media\ Server/Codecs/ -name "libmpeg2video_decoder.so" -printf "%h\n")/"
export LD_LIBRARY_PATH="/usr/lib/plexmediaserver:/usr/lib/plexmediaserver/lib/"
# Grab some dimension and framerate info so we can set bitrates
HEIGHT="$(/usr/lib/plexmediaserver/Plex\ Transcoder -i "${1}" 2>&1 | grep "Stream #0:0" | perl -lane 'print $1 if /, \d{3,}x(\d{3,})/')"
FPS="$(/usr/lib/plexmediaserver/Plex\ Transcoder -i "${1}" 2>&1 | grep "Stream #0:0" | perl -lane 'print $1 if /, (\d+(.\d+)*) fps/')"
# Default (max) bitrate vlaues. Assuming 1080i30 (ATSC max) has same needs as 720p60
ABR="4M"
MBR="8M"
# Use awk to do some number comparison and set some flags
PROFILE=$(echo - | awk "{if ($HEIGHT < 720) {print \"LOW\"} else if ($HEIGHT < 1080) {print \"MEDIUM\"}}")
SPEED=$(echo - | awk "{if ($FPS < 59) {print \"LOW\"} else {print \"HIGH\"}}")
if [ "$PROFILE" == "LOW" ];
then
# This is a pretty deep ATSC sub-channel - not much bitrate
ABR="1M"
MBR="2M"
elif [ "$PROFILE" == "MEDIUM" ];
then
if [ "$SPEED" == "LOW" ];
then
# This may be 720@30
ABR="2M";
MBR="4M";
fi
else
# This may be 720@60
ABR="4M"
MBR="8M"
fi
# Plex Transcoder is based on FFMPEG
# Use NVDEC/NVENC support (built into Plex Pass) to transcode the captured MPEG2/TS
# Output an MKV (h264 w/ original audio)
/usr/lib/plexmediaserver/Plex\ Transcoder -y -hwaccel nvdec -i "${1}" \
-c:v h264_nvenc -rc:v vbr_hq -cq:v 19 -b:v "$ABR" -maxrate:v "$MBR" -profile:v high -bf:v 3 \
-c:a copy "${1%.*}.mkv"
# Grab the duration of the original and transcoded files
SRC_DUR=$(/usr/lib/plexmediaserver/Plex\ Transcoder -i "${1}" 2>&1 | grep "Duration"| cut -d ' ' -f 4 | sed s/,// | sed 's@\..*@@g' | awk '{ split($1, A, ":"); split(A[3], B, "."); print 3600*A[1] + 60*A[2] + B[1] }')
DEST_DUR=$(/usr/lib/plexmediaserver/Plex\ Transcoder -i "${1%.*}.mkv" 2>&1 | grep "Duration"| cut -d ' ' -f 4 | sed s/,// | sed 's@\..*@@g' | awk '{ split($1, A, ":"); split(A[3], B, "."); print 3600*A[1] + 60*A[2] + B[1] }')
# Compare the durations to make sure we got a full transcode (>98% original duration)
CHECK=$(echo - | awk "{if ($DEST_DUR > ($SRC_DUR * 0.98)) print $DEST_DUR}")
# Clean-up the in the event of success or failure
if [ "$CHECK" != "" -a "$DEST_DUR" != "" -a "$DEST_DUR" == "$CHECK" ];
then
echo INFO: Transcode complete. Removing source file.
rm "${1}"
else
echo ERROR: Transcode complete. Source and destination are not the same length. Preserving source.
rm "${1%.*}.mkv"
fi
To find a “Plex Transcoder” command line for your environment simply start an “optimize” process in the Plex UI and look at the process running on your server w/ “ps -ef | grep Trans”. Plex creates a very specific command based on the source media. I’ve greatly simplified it above to be general purpose.
I hope this helps somebody looking to save on storage space when recording w/ their Plex DVR.
Docker Addendum: An approach like the one above can be extra useful when Plex is running as a docker container. It can be difficult to introduce custom transcoding executables into such an environment and persist them across Plex upgrades. In general, you don’t want to touch the docker image contents. A single script (like the one above) can easily be mounted into the container as a volume (I mount to /opt/scripts). From Plex UI, I can call the script from “/opt/scripts/myscript.sh”.