-
-
Save Sixcurses/3f0d04db798db1072b6c968c27b5f778 to your computer and use it in GitHub Desktop.
Script to fix TBA titled episodes in Sonarr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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