-
Smart caching script
Version of this script with latests improvements, still rockin' in 2026 ! (note : I removed transmission section caus' I don't use it, feel free to add it again). #!/bin/bash # This script gets playing sessions from Plex, and tells rsync to copy them from array to cache drive. # It also cleans oldest modified files by rsyncing them back to array (in case of modification). # Hardlink are preserved # # By Reynald mailto:[email protected] # Now works with plex v1.40 or later # WARNING this script must be scheduled carefully one time every 6 minutes max (10 minutes advised), execute it more frequently can damage your SSD # Correction of file permissions by Reynald # By Quinto added analyze of media by plex after syncing to prevent wrong media duration (2hrs bug) # By Quinto Now work with multiple parts movie and with multiple medias movie (eg Same movie in 1080p and 4k) # v.0.6.2 # settings { #Plex # put here the corresponding docker name of your plex App egg binhex-plex, Plex-Media-Server, etc PLEX_DOCKER="Plex-Media-Server" PLEX_TOKEN="xxxxxxxxxxxxxxxxx" PLEX_HOST="192.168.0.240:32400" PLEX_MAX_CACHED_SESSIONS=16 PLEX_CONTAINER_PATH="video_data" #Rsync path & options STORAGE_PATH="/mnt/user0/video_data/" CACHE_PATH="/mnt/plex_smart_cache/video_data/" CACHE_MIN_FREE_SPACE_PCT="94" CACHE_MAX_FREE_SPACE_PCT="60" #Parameters LOG_MAX_SIZE=5000000 NOISY_HOUR_START= NOISY_HOUR_STOP= # if you're not using Youtube-dl agent for plex don't touch this line, it must only be changed when you're using yt-dl custom agent YOUTUBE_DL_AGENT="com.plexapp.agents.youtube-dl" #Options (set to true or false) DRY_RUN=false WRITE_ON_STORAGE=false #number of episodes to cache PLEX_CACHE_NB_EPISODES=6 PLEX_CACHE_SEASON_TILL_END=false VERBOSE=0 #0=Error; 1=Info; 2=More_Info; 3=Debug } ##### No modification below this line ##### CACHE_DISK=$(df -lah "$CACHE_PATH" | awk 'FNR == 2 {print $1}' | tr -d '\n') sys_checks() { # check if plex docker is running plex_running=`docker inspect -f '{{.State.Running}}' $PLEX_DOCKER` if [[ ! $plex_running ]] then echo "Error: Plex docker is not running" rm /var/lock/smart-cache_plex_transmission exit 1 fi # lock if [[ -f /var/lock/smart-cache_plex_transmission ]] then echo "Error: Script already running" rm /var/lock/smart-cache_plex_transmission exit 1 else touch /var/lock/smart-cache_plex_transmission [[ $VERBOSE -ge 2 ]] && echo "Welcome to $0" fi # check that path are mounted if [[ ! -d $STORAGE_PATH ]] || [[ ! -d $CACHE_PATH ]]; then echo "Error: Paths are not accessibles" rm /var/lock/smart-cache_plex_transmission exit 1 fi # cut log LOG_FILE=$(echo $0 | sed 's|\/script|\/log.txt|') echo "log= $LOG_FILE" LOG_SIZE=$(stat -c %s "$LOG_FILE") [[ $VERBOSE -ge 1 ]] && echo "Info: Log size is $LOG_SIZE" if [[ $LOG_SIZE -ge $LOG_MAX_SIZE ]] then [[ $VERBOSE -ge 1 ]] && echo "Info: Emptying log file" echo "" > "$LOG_FILE" fi [[ $VERBOSE -ge 1 ]] && echo "" } ####################### # Transfers functions # ####################### noisy_hours() { # return 0 if time in noisy hour range if [[ $(date '+%-H') -ge $NOISY_HOUR_START ]] && [[ $(date +%-H) -le $NOISY_HOUR_STOP ]] then return 0 else return 1 fi } rsync_transfer() { # get files and path SOURCE_FILE=$1 DEST_FILE=$2 SOURCE_PATH=$3 DEST_PATH=$4 PLEX_ANALYZE_STRING=$5 RS_OPTIONS=$6 [[ $VERBOSE -ge 3 ]] && echo " --- Debug:Rsync_transfer function parameters:" [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Source file: $SOURCE_FILE" [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Dest. file: $DEST_FILE" [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Source path: $SOURCE_PATH" [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Dest. path: $DEST_PATH" [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Plex Analyze string: $PLEX_ANALYZE_STRING" [[ $VERBOSE -ge 3 ]] && echo " ---- Debug: Options : $RS_OPTIONS" # check if original file exist if [[ ! -f "${SOURCE_FILE}" ]] && [[ ! -f "${DEST_FILE}" ]] then echo " --- Error: Files:" echo " ${SOURCE_FILE}" echo " ${DEST_FILE}" echo " does not exist" return 1 elif [[ "${DEST_FILE}" = "${DEST_PATH}" ]] || [[ "${SOURCE_FILE}" = "${SOURCE_PATH}" ]] then echo " --- Error: Cannot sync root path!" return 1 elif [[ ! -f "${SOURCE_FILE}" ]] && [[ "${DEST_PATH}" = "${CACHE_PATH}" ]] && [[ -f "${DEST_FILE}" ]] then if noisy_hours then if [[ ! WRITE_ON_STORAGE ]] then [[ $VERBOSE -ge 2 ]] && echo " --- Info: File is on cache only. Inside noisy hours, sending to storage" rsync_transfer "${DEST_FILE}" "${SOURCE_FILE}" "${DEST_PATH}" "${SOURCE_PATH}" "${PLEX_ANALYZE_STRING}" fi else [[ $VERBOSE -ge 2 ]] && echo " --- Warning: File is on cache only. Outside of noisy hours, doing nothing" fi return elif [[ -f "${DEST_FILE}" ]] && [[ "${DEST_PATH}" = "${CACHE_PATH}" ]] then [[ $VERBOSE -ge 1 ]] && echo " --- Info: File already cached" return fi # get dir SOURCE_DIR=$(dirname "${SOURCE_FILE}") DEST_DIR=$(dirname "${DEST_FILE}") if ! $DRY_RUN then # sync file sudo mkdir -p -m u-x,go-rwx,go+u,ugo+X "${DEST_DIR}" [[ $VERBOSE -ge 1 ]] && echo " --- Info: Syncing ${SOURCE_FILE} to ${DEST_FILE}" sudo nice -n 13 ionice -c 3 /usr/bin/rsync --bwlimit=190000 -aHq "${SOURCE_FILE}" "${DEST_FILE}" if [[ ! $? -eq 0 ]] then echo " --- Error: cannot rsync ${SOURCE_FILE}" echo " to ${DEST_FILE}" return 1 else [[ $VERBOSE -ge 1 ]] && echo " --- Info: Syncing succeeded - refreshing plex media" sleep 0.8 curl --limit-rate 400k --silent "${PLEX_ANALYZE_STRING}" fi # remove original file if requested if [[ "${RS_OPTIONS}" = "--remove-source-files" ]] && $RSYNC_RESULT then [[ $VERBOSE -ge 2 ]] && echo " --- Info: Delete ${SOURCE_FILE}" sudo nice -n 13 ionice -c 3 /usr/bin/rm "${SOURCE_FILE}" fi else echo " --- Running in DRY RUN mode : Files aren't really copied, doing nothing" fi } ################ # Plex Caching # ################ plex_cache() { # get Plex sessions STATUS_SESSIONS=$(nice -n 10 curl --limit-rate 400k --silent http://${PLEX_HOST}/status/sessions -H "X-Plex-Token: $PLEX_TOKEN") if [[ -z $STATUS_SESSIONS ]]; then echo "Error: Cannot connect to plex" return 1 fi NB_SESSIONS=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/@size)' -) echo "----------------------------" echo "$NB_SESSIONS active(s) plex session(s):" echo "----------------------------" # for each session if [[ $NB_SESSIONS -gt $PLEX_MAX_CACHED_SESSIONS ]] then NB_SESSIONS=$PLEX_MAX_CACHED_SESSIONS echo "Warning: Caching is limited to $PLEX_MAX_CACHED_SESSIONS plex sessions (user setting)" fi for i in `seq $NB_SESSIONS` do # get title ID=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@ratingKey)' -) TYPE=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@type)' -) MKEY=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@key)' -) # eventually get serie info if [[ $TYPE = "episode" ]] then TYPE="Serie" GRANDPARENTTITLE=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@grandparentTitle)' -) SEASON=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@parentIndex)' -) TITLE=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@title)' -) EPISODE=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@index)' -) PARENT_ID=$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@parentRatingKey)' -) PLEX_FILE_SESS_TVSHOW=$(nice -n 10 curl --limit-rate 400k --silent http://${PLEX_HOST}/library/metadata/$PARENT_ID/children) PARENT_NB_EPISODES=$(echo $PLEX_FILE_SESS_TVSHOW | xmllint --xpath 'string(//MediaContainer/@size)' -) PARENT_START_EPISODE=$(echo $PLEX_FILE_SESS_TVSHOW | xmllint --xpath 'string(//MediaContainer/Video[1]/@index)' -) SECTION_ID=$(curl --limit-rate 400k --silent http://${PLEX_HOST}/library/metadata/$PARENT_ID | xmllint --xpath 'string(//MediaContainer/@librarySectionID)' -) SECTION_AGENT=$(curl --limit-rate 400k --silent http://${PLEX_HOST}/library/sections?X-Plex-Token= | xmllint --xpath 'string(//MediaContainer/Directory[@key='$SECTION_ID']/@agent)' -) # update nb file to cache START_FILE="$EPISODE" if [[ "${SECTION_AGENT}" = "${YOUTUBE_DL_AGENT}" ]] then TYPE="Youtube" NB_FILES=1 else if [[ $PLEX_CACHE_NB_EPISODES -gt 0 ]]; then NB_FILES=$(( ($EPISODE + $PLEX_CACHE_NB_EPISODES) < $PARENT_NB_EPISODES ? ($EPISODE + $PLEX_CACHE_NB_EPISODES) : $PARENT_NB_EPISODES )) else $PLEX_CACHE_NEXT_EPISODE && NB_FILES=$(( $EPISODE + 1)) fi $PLEX_CACHE_SEASON_TILL_END && NB_FILES=$(( $PARENT_NB_EPISODES )) fi TITLE="$TYPE: ${GRANDPARENTTITLE} Season ${SEASON} - Episode ${EPISODE}/${PARENT_NB_EPISODES}: $TITLE" elif [[ $TYPE = "movie" ]] then TYPE="Movie" TITLE="$(echo $STATUS_SESSIONS | xmllint --xpath 'string(//MediaContainer/Video['$i']/@title)' -)" TITLE="$TYPE: $TITLE" PLEX_FILE_SESS_MOVIE=$(nice -n 10 curl --limit-rate 400k --silent http://${PLEX_HOST}/library/metadata/$ID) else TYPE="Audio" TITLE="track caching not implemented" TITLE="$TYPE: $TITLE" START_FILE=0 NB_FILES=0 fi echo " - $i / $NB_SESSIONS: $TITLE" NB_MEDIAS=$(echo $PLEX_FILE_SESS_MOVIE | xmllint --xpath "count(//Media)" -) NB_FILES=$(echo $PLEX_FILE_SESS_MOVIE | xmllint --xpath "count(//Part)" -) [[ $VERBOSE -ge 2 ]] && echo " -- Info: Number of medias :${NB_MEDIAS}" [[ $VERBOSE -ge 2 ]] && echo " -- Info: Number of files :${NB_FILES}" START_FILE=1 for j in `seq $START_FILE $NB_FILES` do # get file path if [[ $TYPE = "Audio" ]] then [[ $VERBOSE -ge 2 ]] && echo " -- Info: Skipping" else if [[ $TYPE != "Movie" ]] then if [[ $TYPE = "Youtube" ]] then # media is a youtube episode # [[ $VERBOSE -ge 2 ]] && echo " -- Info: Skipping" PLEX_FILE=$(echo "${PLEX_FILE_SESS_TVSHOW}" | xmllint --xpath 'string(//MediaContainer/Video['$(($PARENT_START_EPISODE - $EPISODE +1))']/Media/Part/@file)' -) ID=$(echo "${PLEX_FILE_SESS_TVSHOW}" | xmllint --xpath 'string(//MediaContainer/Video['$(($PARENT_START_EPISODE - $EPISODE +1))']/@ratingKey)' -) else # media is tv show PLEX_FILE=$(echo "${PLEX_FILE_SESS_TVSHOW}" | xmllint --xpath 'string(//MediaContainer/Video['$(($j - $PARENT_START_EPISODE + 1))']/Media/Part/@file)' -) ID=$(echo "${PLEX_FILE_SESS_TVSHOW}" | xmllint --xpath 'string(//MediaContainer/Video['$(($j - $PARENT_START_EPISODE + 1))']/@ratingKey)' -) fi m=$PARENT_NB_EPISODES else # media is a movie if [[ $NB_FILES -gt 1 ]] then if [[ $NB_FILES -gt $NB_MEDIAS ]] then # multiple parts movie (part1, part2, etc) PLEX_FILE=$(echo "${PLEX_FILE_SESS_MOVIE}" | xmllint --xpath 'string(//MediaContainer/Video/Media/Part['$j']/@file)' -) else # multiple medias movie (4k, 1080p, 720p, etc) PLEX_FILE=$(echo "${PLEX_FILE_SESS_MOVIE}" | xmllint --xpath 'string(//MediaContainer/Video/Media['$j']/Part/@file)' -) fi else # movie in single part / format PLEX_FILE=$(echo "${PLEX_FILE_SESS_MOVIE}" | xmllint --xpath 'string(//MediaContainer/Video/Media/Part/@file)' -) fi echo "${PLEX_FILE}" m=$NB_FILES fi PLEX_ANALYZE="http://${PLEX_HOST}/library/metadata/$ID/analyze?X-Plex-Token: ${PLEX_TOKEN}" FILE_TO_CACHE="$(sed -e 's|\"\"|\/|g' -e 's/%/\%/g' <<<${PLEX_FILE} | sed 's|\"||g' | sed 's|\/'${PLEX_CONTAINER_PATH}'\/||')" [[ $VERBOSE -ge 2 ]] && echo " -- Info: Streaming File $j/$m: ${FILE_TO_CACHE}" STORAGE_FILE="${STORAGE_PATH}${FILE_TO_CACHE}" CACHE_FILE="${CACHE_PATH}${FILE_TO_CACHE}" # and send to rsync rsync_transfer "${STORAGE_FILE}" "${CACHE_FILE}" "${STORAGE_PATH}" "${CACHE_PATH}" "${PLEX_ANALYZE}" fi sleep .1 done sleep .1 done sleep .1 } #################### # Delete old files # #################### cleanup() { # get free space sa=$(df -lah | grep $CACHE_DISK | awk '{ printf "%d", $5 }') sb=$CACHE_MIN_FREE_SPACE_PCT echo "---------------------" echo "Cache disk usage: ${sa}%" echo "---------------------" if [[ "$sa" -ge "$sb" ]]; then echo "$sa% space used, quota is $sb%, cleaning" [[ $VERBOSE -ge 1 ]] && echo "Info: Scanning files..." # get oldest accessed files find "${CACHE_PATH}" -type f -printf "%C@ %p\n" | sort -n | sed "s|`echo ${CACHE_PATH}`|%|g" | cut -d'%' -f2 | while read FILE_TO_CLEAN do # loop start: get free space again sa=$(df -lah | grep $CACHE_DISK | awk '{ printf "%d", $5 }') sb=$CACHE_MAX_FREE_SPACE_PCT # if free space not enough if [[ "$sa" -ge "$sb" ]]; then [[ $VERBOSE -ge 1 ]] && echo " - Info: $sa% space used, target $sb%, uncaching $FILE_TO_CLEAN" STORAGE_FILE="${STORAGE_PATH}${FILE_TO_CLEAN}" CACHE_FILE="${CACHE_PATH}${FILE_TO_CLEAN}" # sync back cache to storage rsync_transfer "${CACHE_FILE}" "${STORAGE_FILE}" "${CACHE_PATH}" "${STORAGE_PATH}" "--remove-source-files" fi # loop done fi sa=$(df -lah | grep $CACHE_DISK | awk '{ printf "%d", $5 }') sb=$CACHE_MIN_FREE_SPACE_PCT [[ $VERBOSE -ge 1 ]] && echo " - Info: $sa% space used, quota is $sb%, nothing to do" # prune empty directories from source dir [[ $VERBOSE -ge 2 ]] && echo " -- Info: Cleaning empty directories..." find "${CACHE_PATH}" -type d -not -path '*/\.*' -empty -prune -exec rmdir --ignore-fail-on-non-empty {} \; [[ $VERBOSE -ge 1 ]] && echo "" } sys_checks plex_cache cleanup rm /var/lock/smart-cache_plex_transmission exit 0
-
Node 804, low power, transcoding and many drives
it's NEXUS DOUBLETWIN | HARD DRIVE VIBRATION ISOLATOR : https://silentpc.com/quiet-pc-accessories/doubletwin
-
ata9.00: failed command: READ FPDMA QUEUED error
ST14000VN0008-2JG101 is the serial number of the involved hard drive, you can try to reseat the sata cable or to change it...
-
ata9.00: failed command: READ FPDMA QUEUED error
take a look at this thread : https://forums.unraid.net/topic/26050-idenitifying-which-drive-is-quotata9quot-throwing-errors-into-syslog/
-
ata9.00: failed command: READ FPDMA QUEUED error
it can be bad sata connexion or bad sata cable, or too much vibration during the parity check which can mess with the sata cable, etc...
-
Smart caching script
Just a little update, the script is broken with the latests Plex versions, i'm working on an update, for jellyfin sorry i don't use that at the moment, so, i can't work on it...
-
Plex transcode
yup these are the correct settings, you will notice a slight impact on ram usage but it's hard to notice only with one transoding for only one user...
-
Plex is buffering while transcoding ( i7-4790K)
weird problem, as a temporary workaround you can downgrade to older version if you want, just put "1.29.1.6316-f4cdfea9c-ls139" in the VERSION variable in your Plex docker settings...
-
Plex is buffering while transcoding ( i7-4790K)
can you try with a video with .SRT subtitles ? I think the problem can be PGS subtitles...
-
Plex is buffering while transcoding ( i7-4790K)
i think this video will help you: Unraid - Enabling Intel Quick Sync for Plex Transcoding (and other containers) : https://www.youtube.com/watch?v=tmsXyFVdRAs
-
Plex is buffering while transcoding ( i7-4790K)
do you use "Intel-GPU-TOP" plugin ?
-
Plex is buffering while transcoding ( i7-4790K)
it must be confusion, latest plex version should be : 1.29.1 at first sight your docker settings seems ok, have you ever used specific script to enable quicksync at the start of the plex docker ?
-
Plex is buffering while transcoding ( i7-4790K)
What's the version number of your plex media server ? Can you show us the complete settings of your Plex-Media-Server docker?
-
[SOLVED] Strange issue : Dashboard Memory usage show 0% after a while...
all seems normal now, problem is gone after uninstalling "python3.plg" and "python2.plg" and reinstalling python via NerdTools plugin...
-
GPU temperature & statistiques
Tu devrais te pencher sur les plugins "gpu statistics" et "Radeon-TOP" je pense que tu trouveras ton bonheur...
doobyns
Members
-
Joined
-
Last visited