Skip to content

Instantly share code, notes, and snippets.

@thornbill
Last active April 6, 2025 04:50
Show Gist options
  • Save thornbill/eb97761472cd0285106a98c101eff962 to your computer and use it in GitHub Desktop.
Save thornbill/eb97761472cd0285106a98c101eff962 to your computer and use it in GitHub Desktop.
DVR post processing script for Jellyfin
#!/usr/bin/env bash
set -o errexit
set -o pipefail
set -o nounset
# set -o xtrace
PWD="$(pwd)"
die () {
echo >&2 "$@"
cd "${PWD}"
exit 1
}
# Colors
GREEN='\033[0;32m'
NC='\033[0m' # No Color
__path="${1:-}"
# verify a path was provided
[ -n "$__path" ] || die "path is required"
# verify the path exists
[ -f "$__path" ] || die "path ($__path) is not a file"
__dir="$(dirname "${__path}")"
__file="$(basename "${__path}")"
__base="$(basename "${__path}" ".ts")"
# Debbuging path variables
# printf "${GREEN}path:${NC} ${__path}\ndir: ${__dir}\nbase: ${__base}\n"
# Try to find local version of ffmpeg, defaults to the path used in docker if not found
__ffmpeg="$(which ffmpeg || echo '/usr/lib/jellyfin-ffmpeg/ffmpeg')"
# Change to the directory containing the recording
cd "${__dir}"
# Extract closed captions to external SRT file
printf "[post-process.sh] %bExtracting subtitles...%b\n" "$GREEN" "$NC"
$__ffmpeg -f lavfi -i movie="${__file}[out+subcc]" -map 0:1 "${__base}.srt"
# Transcode to mp4, crf parameter can be adjusted to change output quality
printf "[post-process.sh] %bTranscoding file..%b\n" "$GREEN" "$NC"
$__ffmpeg -i "${__file}" -vcodec libx264 -vf yadif=parity=auto -crf 20 -preset veryslow "${__base}.mp4"
# Remove the original recording file
printf "[post-process.sh] %bRemoving originial file...%b\n" "$GREEN" "$NC"
rm "${__file}"
# Return to the starting directory
cd "${PWD}"
@MapGuy11
Copy link

Any way to get the original file removed after the conversion is done.

@thornbill
Copy link
Author

@MapGuy11 👍 I have updated the script to remove the original file and added some comments about what each step is doing.

@MapGuy11
Copy link

Wow Thank you!

@CFusionMM
Copy link

i got a windows jellyfin server, would this work on that? I cant seem to get it working

@MapGuy11
Copy link

No , this script is linux based. If you are stuck on Windows use Windows Subsystem for Linux to install Jellyfin on Linux with Windows OS Install.

@Alucard316
Copy link

I whipped together a very quick and dirty windows version for myself, figured I'd link it here for people that stumble across this (like I did) but need it in Windows.

Windows DVR PostProcess Batch Script for Jellyfin

@tge101
Copy link

tge101 commented Apr 12, 2021

Any plans to add comskip?

@thornbill
Copy link
Author

It’s something that I have considered, but I haven’t had a chance to try it yet.

@Artiume
Copy link

Artiume commented Aug 30, 2021

Found this, might help debug the character ecaping issue

http://underpop.online.fr/f/ffmpeg/help/quoting-and-escaping.htm.gz

The function av_get_token defined in 'libavutil/avstring.h' can be used to parse a token quoted or escaped according to the rules defined above.

The tool 'tools/ffescape' in the FFmpeg source tree can be used to automatically quote or escape a string in a script.

@bwarden
Copy link

bwarden commented Sep 9, 2021

I found this awkward quoting process (inspiration from ffmpeg#5896) helped with normal escaping, plus the extra touches the filter parameter requires for single ticks (') and colons (:) in filenames for the subtitle extraction command:

$__ffmpeg -f lavfi -i movie="$(printf %q "${__file//\'/\\\'}" | sed "s#\:#\\\\\\\\:#")[out+subcc]" -map 0:1 "${__base}.srt"

I'm also poking at muxing to mkv instead of mp4, and detecting whether the incoming TS needs transcoding at all, or just remuxing:

+# Decide whether we need to transcode video or audio
+VCODEC=$(ffprobe -loglevel error -select_streams v -show_entries stream=codec_name -of default=nw=1:nk=1 "${__file}" | sort -u)
+ACODEC=$(ffprobe -loglevel error -select_streams a -show_entries stream=codec_name -of default=nw=1:nk=1 "${__file}" | sort -u)
+
+ENCODE_ARGS=""
+if [[ "$VCODEC" != "h264" ]]; then
+       ENCODE_ARGS="$ENCODE_ARGS -c:v libx264"
+else
+       ENCODE_ARGS="$ENCODE_ARGS -c:v copy"
+fi
+if [[ "$ACODEC" != "ac3" ]]; then
+       ENCODE_ARGS="$ENCODE_ARGS -c:a aac"
+else
+       ENCODE_ARGS="$ENCODE_ARGS -c:a copy"
+fi
 # Transcode to mp4, crf parameter can be adjusted to change output quality
 printf "[post-process.sh] %bTranscoding file..%b\n" "$GREEN" "$NC"
-$__ffmpeg -i "${__file}" -vcodec libx264 -vf yadif=parity=auto -crf 20 -preset veryslow "${__base}.mp4"
+$__ffmpeg -i "${__file}" -map 0 $ENCODE_ARGS "${__base}.mkv"

@wanghan0501
Copy link

However, ffprobe cannot be found @bwarden

@AndrewBreyen
Copy link

could be totally missing something, but how do you have this set in DVR settings? I keep getting an exit code of 1 in JF logs: set up like

Post-processing application:
/bin/bash
Post-processor command line arguments:
/Scripts/livetv_postprocess.sh {path}

@jwt4000
Copy link

jwt4000 commented Aug 19, 2023

Your script is awesome! Could you help me with the encode syntax to enable intel quick sync? I am unfortunately running Jellyfin on a lower powered CPU and QSV helps a ton. Thanks!!

@clockwinder
Copy link

It’s something that I have considered, but I haven’t had a chance to try it yet.

Had a chance to try it out yet?

@edwhardo
Copy link

I found this when I had the same problem with a comma in the filename. I posted what worked for me in another github
Protektor-Desura/jellyfin-dvr-comskip#4
Also, wanghan0501 you need to define a variable for __ffprobe (just like __ffmpeg) because its not in the path

@clockwinder
Copy link

@bwarden or @edwhardo Could you please post a gist of your complete script? I'm not completely clear on how to insert your fixes into this script.

@edwhardo
Copy link

edwhardo commented Apr 4, 2025

My script is totally different and probably not of use to you. The only reason I posted was to point out that changes suggested by @bwarden wouldn't work for some if ffprobe is not in the PATH.

I would suggest using the style of the original script by @thornbill and creating a variable at line 36, right after where __ffpmeg is defined. If the binary is in the same path as ffmpeg, you'd do this:
__ffprobe="$(which ffprobe || echo '/usr/lib/jellyfin-ffmpeg/ffprobe')"
This sets the variable ffprobe by first looking for the ffprobe binary in your path and if not there hard wires it to the quoted string after the echo.

Then, when you edit the script to call ffprobe, use $__ffprobe instead of just ffprobe

If you're curious as to why @bwardens code has + at the beginning of the line, he posted the output of a diff command. This says that his script has these additional lines to that of @thornbill and if you were to manually edit, you wouldn't put the + at the beginning of a line.

@clockwinder
Copy link

clockwinder commented Apr 5, 2025

@edwhardo Thanks for the info. I'm only curious about resolving the failure of special characters ' ; :, which seems to be the first code block here, and the second code block only seems related to detecting if transcoding is necessary.

If anyone has a version of this script which doesn't fail when ', ;, or : are in the title, please send it my way!

@AndrewBreyen
Copy link

@clockwinder

If anyone has a version of this script which doesn't fail when ', ;, or : are in the title, please send it my way!

I moved to using a python script to make it easier to do more things easier, it's definitely not optimized, but you could add an additional case to my code here:
https://github.com/AndrewBreyen/Jellyfin-TV-Post-Process/blob/6b29fa493e3c2c34387b2f7b5e9a74626846300e/macmini/record_post_process.py#L77

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment