Skip to content

Instantly share code, notes, and snippets.

@Sixcurses
Forked from goose-ws/fix-tba.sh
Last active August 29, 2024 20:27
Show Gist options
  • Save Sixcurses/3f0d04db798db1072b6c968c27b5f778 to your computer and use it in GitHub Desktop.
Save Sixcurses/3f0d04db798db1072b6c968c27b5f778 to your computer and use it in GitHub Desktop.
Script to fix TBA titled episodes in Sonarr
#!/bin/bash
#description=Fixes TBA episodes leftover by sonarr
#name=Fix-TBA
#arrayStarted=true
# Minor edits to work with Unraid User scripts plugin
# Changed docker exec -it >> docker exec -t
# Add via webui and set hourly task
# or add it to /boot/config/plugins/user.scripts/scripts/fixTBA and set hourly
#################################################################################
## About
# Are you a person who sometimes has to manually import episodes because they don't yet have actual titles in
# their metadata, and their title shows as "TBA" in Sonarr? And then once you import the files, they live in
# your library under their "TBA" titles until you go through the effort of manually renaming them? Then perhaps
# this script is for you.
# The purpose of this script is to check for any files in your Sonarr library which have the title "TBA",
# and rename them to their actual name, if that metadata is available. It assumes you are using Sonarr in
# Docker, that the user running this script can access the Docker socket, and that you are using either the
# linuxserver/sonarr image or the hotio/sonarr image. This script is meant to be run on the bare metal of
# the system, not inside the docker container. This script works by:
# 1. Searching for any files that have TBA in the title
# 2. Finding the Series ID for that series in Sonarr
# 3. Preforming a metadata refresh for that series in Sonarr
# 4. Preforming a rename for that series in Sonarr
# I use this on an hourly cron script, which is probably fine for what I'm trying to accomplish.
# There are a few requirements for this script to work correctly. Firstly, it may help for you to set Sonarr
# to allow automatic import of files under their TBA title. This can be done under: Settings > Media Management
# Episode Title Requires > (Only for Bulk Season Releases / Never)
# I use "Only for Bulk Season Releases". Either of these settings will allow Sonarr to import files if their
# title is TBA due to metadata not yet being updated for the episode.
# Next, this script relies on finding TBA files with the search patthern: "* TBA *"
# I suggest having the Episode Clean Title in the "Episode Format". This means that at a minimum,
# you must have: {Episode CleanTitle}
# in your "Episode Format" fields. I use the Episode Formats:
# Standard Episode Format: {Series TitleYear} - S{season:00}E{episode:00} - {Episode CleanTitle} [{Preferred Words }{Quality Full}]{[MediaInfo VideoDynamicRange]}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{MediaInfo AudioLanguages}{-Release Group}
# Daily Episode Format: {Series TitleYear} - {Air-Date} - {Episode CleanTitle} [{Preferred Words }{Quality Full}]{[MediaInfo VideoDynamicRange]}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}{[Mediainfo AudioCodec}{ Mediainfo AudioChannels]}{MediaInfo AudioLanguages}{-Release Group}
# Anime Episode Format: {Series TitleYear} - S{season:00}E{episode:00} - {absolute:000} - {Episode CleanTitle} [{Preferred Words }{Quality Full}]{[MediaInfo VideoDynamicRange]}[{MediaInfo VideoBitDepth}bit]{[MediaInfo VideoCodec]}[{Mediainfo AudioCodec} { Mediainfo AudioChannels}]{MediaInfo AudioLanguages}{-Release Group}
# Thanks to Trash Guides ( https://trash-guides.info/Sonarr/Sonarr-recommended-naming-scheme/ ) for these naming schemes.
#################################################################################
## Config - Edit this section
# Docker container name
containerName="sonarr"
#################################################################################
## Source - Do not edit below this line
# Check dependencies
depArr=("curl" "do" "docker" "echo" "elif" "exit" "for" "grep" "if" "jq" "readarray" "then" "tr" "wc")
depFail="0"
for i in "${depArr[@]}"; do
if [[ "${i:0:1}" == "/" ]]; then
if ! [[ -e "${i}" ]]; then
echo "${i}\\tnot found"
depFail="1"
fi
else
if ! command -v ${i} > /dev/null 2>&1; then
echo "${i}\\tnot found"
depFail="1"
fi
fi
done
if [[ "${depFail}" -eq "1" ]]; then
echo "Dependency check failed"
exit 255
fi
# Get sonarr's IP address
sonarrIP="$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "${containerName}")"
if ! [[ "${sonarrIP}" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Failed to obtain IP address"
exit 2
fi
# Get a copy of Sonarr's config
sonarrConfig="$(docker exec -t ${containerName} cat /config/config.xml)"
if [[ -z "${sonarrConfig}" ]]; then
echo "Failed to read Sonarr config file"
exit 3
fi
# Get the port Sonarr is using
sonarrPort="$(grep -Eo "<Port>.*</Port>" <<<"${sonarrConfig}")"
sonarrPort="${sonarrPort#<Port>}"
sonarrPort="${sonarrPort%</Port>}"
if ! [[ "${sonarrPort}" =~ ^[0-9]+$ ]]; then
echo "Failed to obtain port"
exit 4
fi
# Get the API key Sonarr is using
sonarrApiKey="$(grep -Eo "<ApiKey>.*</ApiKey>" <<<"${sonarrConfig}")"
sonarrApiKey="${sonarrApiKey#<ApiKey>}"
sonarrApiKey="${sonarrApiKey%</ApiKey>}"
if [[ -z "${sonarrApiKey}" ]]; then
echo "Failed to read Sonarr API key"
exit 5
fi
# Get the URL base Sonarr is using
sonarrUrlBase="$(grep -Eo "<UrlBase>.*</UrlBase>" <<<"${sonarrConfig}")"
sonarrUrlBase="${sonarrUrlBase#<UrlBase>}"
sonarrUrlBase="${sonarrUrlBase%</UrlBase>}"
# Ensure we can use the Sonarr API
apiCheck="$(curl -sL "${sonarrIP}:${sonarrPort}${sonarrUrlBase}/api/system/status?apikey=${sonarrApiKey}")"
if [[ "${?}" -ne "0" ]]; then
echo "Curl failed"
exit 6
elif grep -q '"error": "Unauthorized"' <<<"${apiCheck}"; then
echo "API failure"
exit 7
fi
# Find out what the libraries are inside the container
# Get the API output once, and store it as a variable
libraries="$(curl -sL "${sonarrIP}:${sonarrPort}${sonarrUrlBase}/api/rootfolder?apikey=${sonarrApiKey}")"
# Count how many libraries we have
numLibraries="$(jq -M length <<<"${libraries}")"
# Add each library to an array
for i in $(seq 0 $(( numLibraries - 1 ))); do
item="$(jq -M ".[${i}].path" <<<"${libraries}")"
item="${item#\"}"
item="${item%\"}"
libraryArr+=("${item}")
done
# For each item in the array, search for any files which have "* TBA *" in the title
for i in "${libraryArr[@]}"; do
files+=("$(docker exec -t ${containerName} find "${i}" -type f -name "* TBA *" | tr -d '\r')")
done
for file in "${files[@]}"; do
# Quick check to ensure that we actually need to do this. Perhaps there were multiple TBA's in a series, and we got all of them on the first run?
readarray -t dirContents < <(docker exec -t ${containerName} ls "${file%/*}" | tr -d '\r')
fileExists="0"
for i in "${dirContents[@]}"; do
i="${i#\'}"
i="${i%\'}"
if [[ "${i}" == "${file##*/}" ]]; then
fileExists="1"
fi
done
if [[ "${fileExists}" -eq "1" ]]; then
# Find the series ID by searching for a series with the matching path
# First we have to extract ${seriesPath} from ${file}
# Get the root folder
rootFolder="${file#/}"
rootFolder="${rootFolder%%/*}"
# Next get the series folder
seriesFolder="${file#/"${rootFolder}"/}"
seriesFolder="${seriesFolder%%/*}"
seriesPath="/${rootFolder}/${seriesFolder}"
# Find the series which matches the path
series="$(curl -sL "${sonarrIP}:${sonarrPort}${sonarrUrlBase}/api/series?apikey=${sonarrApiKey}" | jq -M ".[] | select(.path==\"${seriesPath}\")")"
# Get the series ID for the series
seriesId="$(jq -M ".id" <<<"${series}")"
# Ensure we only matched one series
if [[ "$(wc -l <<<"${seriesId}")" -eq "0" ]]; then
echo "Zero series ID matches"
echo "File: ${file}"
exit 9
elif [[ "$(wc -l <<<"${seriesId}")" -gt "1" ]]; then
echo "Too many series ID matches"
echo "File: ${file}"
exit 10
fi
# Refresh the series
curl -sL "${sonarrIP}:${sonarrPort}${sonarrUrlBase}/api/command?apikey=${sonarrApiKey}" -d "{name: \"RefreshSeries\", seriesId: \"${seriesId}\"}" -H "Content-Type: application/json" -X POST >/dev/null 2>&1
# Rename all files within the series
curl -sL "${sonarrIP}:${sonarrPort}${sonarrUrlBase}/api/command?apikey=${sonarrApiKey}" -d "{name: \"RenameSeries\", seriesIds: [${seriesId}]}" -H "Content-Type: application/json" -X POST >/dev/null 2>&1
# Wait for the change to happen
sleep 10
fi
# Check to see if rename happenedreadarray -t dirContents < <(docker exec -t ${containerName} ls "${file%/*}")
readarray -t dirContents < <(docker exec -t ${containerName} ls "${file%/*}" | tr -d '\r')
fileExists="0"
for i in "${dirContents[@]}"; do
i="${i#\'}"
i="${i%\'}"
if [[ "${i}" == "${file##*/}" ]]; then
fileExists="1"
fi
done
if [[ "${fileExists}" -eq "0" ]]; then
msgArr+=("Renamed ${file##*/}")
fi
done
if [[ "${#msgArr[@]}" -ne "0" ]]; then
for i in "${msgArr[@]}"; do
echo "${i}"
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment