Skip to content

Instantly share code, notes, and snippets.

@rustymyers
Last active June 7, 2022 08:18
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save rustymyers/a68f405909224e023088c8cac6834c32 to your computer and use it in GitHub Desktop.
Save rustymyers/a68f405909224e023088c8cac6834c32 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3
"""
#------------------------------------------------------------------------------------------------
#-- Movie Tag Updater for Radarr
#--------------------------------------------------------------------------------------------------
# Program : Radarr_tagarr
# To Complie : n/a
#
# Purpose : Add tags and profiles to radarr movies. Tags are retreived from Radarr.
# Say Y to add a tag. N to skip a tag. N for all tags will remove all tags.
#
# Called By :
# Calls :
#
# Author : Rusty Myers <rustymyers@gmail.com>
# Based Upon :
#
# Note :
#
# Revisions :
# 2017-05-03 <rusty> Initial Version
# 2017-05-03 <rusty> Updated to dynamically retreive tags and bulk set the ones chosen
# 2018-02-13 <rusty> New method to check for matching tags without concern for order,
# Adding code to combine tags with existing movie tags instead of replace,
# Updating hostname fields to make it easier to support http or https
# 2019-07-26 <rusty> Updating to Python3
# 2020-08-25 <rusty> Adding --dry-run for testing changes
# 2020-09-01 <rusty> Adding name resolution for hostname
#
# Version : 1.5
#------------------------------------------------------------------------------------------------"""
#
from __future__ import print_function
import requests, argparse, socket
# Function to return json of all movies
def get_data(radarrhost):
"""Get data from API"""
response = requests.get(radarrhost)
return response.json()
# function to put updated data
def put_data(radarrhost, moviedata):
"""Put data into API"""
result = requests.put(radarrhost, json=moviedata)
return result
# Variables
# Radarr API Key, get it in Settings->General->Security of Sonarr pages
APIKEY = ""
# Radarr Host Name: Add reverse proxy or direct IP:port, no trailing slash
HOSTNAME = ""
# Set dryrun default to false
dryrun = False
# Get our arguments
parser = argparse.ArgumentParser(description='Check, Print, and Save DHCP & BSDP Information.')
parser.add_argument('-d', '--dry-run', action='store_true', \
dest='dryrun', help='Dry run of tagging, displays expected outcomes.')
parser.add_argument('-k', '-apikey' , action='store', default=APIKEY, \
dest='APIKEY', help='Path to plist for saving results to plist. Default: /Library/Preferences/edu.psu.sdt.plist')
parser.add_argument('-n', '--hostname', action='store', default=HOSTNAME, \
dest='HOSTNAME', help='Test code with stored BSDP response from OS X Server, writing to /tmp/org.network.plist.')
args = parser.parse_args()
if args.dryrun:
print("!!! Dry Run...not making any changes...")
dryrun = True
if args.APIKEY:
APIKEY = args.APIKEY
if args.HOSTNAME:
HOSTNAME = args.HOSTNAME
host_address = ""
HOST = "".join(HOSTNAME.split("/")[2:3])
try:
host_address = socket.gethostbyname(HOST)
except:
print("Unable to resolve hostname! {}".format(HOST))
exit(1)
# Get All Tags
ALLTAGSJSON = get_data(HOSTNAME + "/api/tag?apikey=" + APIKEY)
# Get all Profiles
ALLPROFILESJSON = get_data(HOSTNAME + "/api/profile?apikey=" + APIKEY)
print("Connecting to: {}".format(HOSTNAME + "/api/tag?apikey=" + APIKEY))
# Set a profile
PROFILEINT = ""
PROFILENAMES = {}
# Print Profiles
for profile in ALLPROFILESJSON:
if profile == "error":
print("Could not connect to Radarr!")
exit(0)
print("Which profile should we add to the movie?")
print("ID: {1} Name: {0}".format(profile["name"], profile["id"]))
# Add tag to lookup by it's id
PROFILENAMES[profile["id"]] = profile["name"]
# Ask if we should add it to all movies?
ADDPROFILE = input('Which profile to all movies? [1-{0}] : '.format(len(PROFILENAMES)))
# Add the profiles to our list of tag numbers
PROFILEINT = int(ADDPROFILE)
ADDPROFILE = input('Are you sure you want to set all movies to profile \"{0}\"? [y/n] : '.format(PROFILENAMES[PROFILEINT]))
# If the answer not starts with any case Y
if not ADDPROFILE.lower().startswith("y"):
print("Skipping profile...")
else:
print("Adding profile {0}...".format(PROFILENAMES[PROFILEINT]))
# Make list of tags to add
TAGS = []
# Make a tag lookup for later
TAGNAMES = {}
# For each tag
for tag in ALLTAGSJSON:
# Add tag to lookup by it's id
TAGNAMES[tag['id']] = tag['label']
# Ask if we should add it to all movies?
ADDTAG = input('Add Tag \"{0}\" to all movies? [y/n] : '.format(tag['label']))
# If the answer starts with any case Y
if ADDTAG.lower().startswith("y"):
# Add the tag to our list of tag numbers
TAGS.append(int(tag['id']))
# Ask if we should contiunue?
ADDTAG = input("Add Tag(s) '{0}' to all movies? [y/n] : ".format(", ".join(TAGNAMES[int(p)] for p in TAGS)))
# If the answer starts with any case Y
if ADDTAG.lower().startswith("y"):
# Get All Movies
ALLMOVIESJSON = get_data(HOSTNAME + "/api/movie?apikey=" + APIKEY)
# for each movie
for movie in ALLMOVIESJSON:
# get movie ID for updating data with put_data
movieID = movie['id']
# Set tagMissing to false and check each tag
dataMissing = False
# For each tag we want to add
for tag in TAGS:
# If that tag does not exist currenlty
if tag not in movie['tags']:
# Set boolean to update tags
dataMissing = True
else:
TAGLIST = ", ".join(TAGNAMES[int(p)] for p in movie['tags'])
print("Found existing tags: {}".format(TAGLIST))
if movie['profileId'] != PROFILEINT:
dataMissing = True
if dryrun:
TAGLIST = ", ".join(TAGNAMES[int(p)] for p in movie['tags'])
MOVIETITLE = movie['title'].encode('utf-8')
print("!!! Dry-Run: Movie {0} has existing tags: {1} ".format(MOVIETITLE, TAGLIST))
if not dataMissing:
# Print movies that have the correct tags
TAGLIST = ", ".join(TAGNAMES[int(p)] for p in movie['tags'])
MOVIETITLE = movie['title'].encode('utf-8')
print("Skipping {0} with existing tags: {1} ".format(MOVIETITLE, TAGLIST))
else:
# Print name to statisfy user something is happening
TAGLIST = ", ".join(TAGNAMES[int(p)] for p in TAGS)
MOVIETITLE = movie['title'].encode('utf-8')
PNAMES = PROFILENAMES[PROFILEINT]
print("Adding '{0}' tags and {2} profile to Movie: {1}"\
.format(TAGLIST, MOVIETITLE, PNAMES))
# Combine movie tags and new tags
tag_set = list(set(movie['tags']+TAGS))
# Add unique tags
movie['tags'] = tag_set
movie['profileId'] = PROFILEINT
# Update radarrHost to include movie ID
radarrHost = HOSTNAME + "/api/movie/" + str(movieID) + "?apikey=" + APIKEY
if dryrun:
print("!!! Dry-run, not updating movie: {}!".format(MOVIETITLE))
print("CHANGES: Adding '{0}' tags and '{2}' profile to Movie: {1}".format(TAGLIST, MOVIETITLE, PNAMES))
# print(movie)
else:
# Put movie back to radarr
updateMovie = put_data(radarrHost, movie)
# If the update worked, just tell 'em that
if updateMovie.status_code == 202:
print("Sucessfully updated \"{0}\"".format(movie['title'].encode('utf-8')))
else:
# If the update didn't work, let 'em know
MOVIETITLE = movie['title'].encode('utf-8')
STATUSCODE = updateMovie.status_code
print("Failed: \"{0}\" returned response code: {1}".format(MOVIETITLE, STATUSCODE))
if b"Dragnet" in movie['title'].encode():
exit(0)
@bidulle95
Copy link

bidulle95 commented Oct 4, 2017

Hello I would like to know how to install this and how it is run on a nas? thank you

@xxcamouflagexx
Copy link

Anything I'm doing wrong here?
I entered my API and hostname variables and then tried running the script.

C:\Users\elesn\Google Drive\Scripts>python RadarrTagarr.py
Traceback (most recent call last):
File "RadarrTagarr.py", line 49, in
allTagsJSON = getData(hostName + "/radarr/api/tag?apikey=" + apiKey)
File "RadarrTagarr.py", line 36, in getData
json = response.json()
File "C:\Python27\lib\site-packages\requests\models.py", line 896, in json
return complexjson.loads(self.text, **kwargs)
File "C:\Python27\Lib\json_init_.py", line 339, in loads
return _default_decoder.decode(s)
File "C:\Python27\Lib\json\decoder.py", line 364, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "C:\Python27\Lib\json\decoder.py", line 382, in raw_decode
raise ValueError("No JSON object could be decoded")
ValueError: No JSON object could be decoded

Thanks and apologize if I'm not commenting in the appropriate location.

@rustymyers
Copy link
Author

@xxcamoulfagexx It looks like you might not have any tags? That might be why the ValueError occurs. Try adding a tag and running it again?

@ak2766
Copy link

ak2766 commented Aug 22, 2020

Any chance of adding a dry-run switch?

The reasoning is that if I've already set a tag, I want to make sure that it is not overwritten.

@rustymyers
Copy link
Author

@ak2766 Try version 1.4. You can now pass variables to the command line, including --dry-run or -d:
./radarr_tagarr3.py --dry-run -k "myAPIkey21384y01h" -n "https://radarr.com/myhost"

@hughesjs
Copy link

hughesjs commented Aug 26, 2020

@rustymyers I've tried running this:

./radarr_tagarr3.py --dry-run -k "mykey" -n "https://radarr.mydomain.co.uk"

But I get an error on conn=connection.create_connection... Any ideas?

My radarr is behind a reverse proxy.

@rustymyers
Copy link
Author

@hughesjs Can you load a URL like this in your browser: https://radarr.mydomain.co.uk/api/profile?apikey=mykey

@hughesjs
Copy link

It returns some JSON aye.

Here's the full error:


Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/urllib3/connection.py", line 159, in _new_conn
    conn = connection.create_connection(
  File "/usr/lib/python3.8/site-packages/urllib3/util/connection.py", line 61, in create_connection
    for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
  File "/usr/lib/python3.8/socket.py", line 918, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 670, in urlopen
    httplib_response = self._make_request(
  File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 381, in _make_request
    self._validate_conn(conn)
  File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 978, in _validate_conn
    conn.connect()
  File "/usr/lib/python3.8/site-packages/urllib3/connection.py", line 309, in connect
    conn = self._new_conn()
  File "/usr/lib/python3.8/site-packages/urllib3/connection.py", line 171, in _new_conn
    raise NewConnectionError(
urllib3.exceptions.NewConnectionError: <urllib3.connection.HTTPSConnection object at 0x7fdc0ca7cbe0>: Failed to establish a new connection: [Errno -2] Name or service not known

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/requests/adapters.py", line 439, in send
    resp = conn.urlopen(
  File "/usr/lib/python3.8/site-packages/urllib3/connectionpool.py", line 726, in urlopen
    retries = retries.increment(
  File "/usr/lib/python3.8/site-packages/urllib3/util/retry.py", line 439, in increment
    raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='hostname', port=443): Max retries exceeded with url: /api/tag?apikey=API (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7fdc0ca7cbe0>: Failed to establish a new connection: [Errno -2] Name or service not known'))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "./radarr_tagarr3.py", line 53, in <module>
    ALLTAGSJSON = get_data(HOSTNAME + "/api/tag?apikey=" + APIKEY)
  File "./radarr_tagarr3.py", line 36, in get_data
    response = requests.get(radarrhost)
  File "/usr/lib/python3.8/site-packages/requests/api.py", line 76, in get
    return request('get', url, params=params, **kwargs)
  File "/usr/lib/python3.8/site-packages/requests/api.py", line 61, in request
    return session.request(method=method, url=url, **kwargs)
  File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 530, in request
    resp = self.send(prep, **send_kwargs)
  File "/usr/lib/python3.8/site-packages/requests/sessions.py", line 643, in send
    r = adapter.send(request, **kwargs)
  File "/usr/lib/python3.8/site-packages/requests/adapters.py", line 516, in send
    raise ConnectionError(e, request=request)
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='hostname', port=443): Max retries exceeded with url: /api/tag?apikey=API (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7fdc0ca7cbe0>: Failed to establish a new connection: [Errno -2] Name or service not known'))

@rustymyers
Copy link
Author

@hughesjs Looks like it's your host name not being able to resolve to an IP address. I can reproduce this is I use a HOSTNAME that doesn't exist. For example: radarr_tagarr3.py -d -k "KEY" -n "http://1234example.com/radarr"

Make sure you can resolve your hostname from the terminal or try using the IP address. Hope that helps!
Rusty

@hughesjs
Copy link

hughesjs commented Aug 27, 2020 via email

@rustymyers
Copy link
Author

@hughesjs Try version 1.5. I added a check to see if it could resolve the hostname when run:

/radarr_tagarr3.py -d -k "KEY" -n "http://1234example.com/radarr"
!!! Dry Run...not making any changes...
Unable to resolve hostname! 1234example.com

See if you get a similar error for your host. Feel free to email me directly your hostname, too, if you want me to see if I can determine the issue.
Thanks

@ak2766
Copy link

ak2766 commented Sep 8, 2020

@ak2766 Try version 1.4. You can now pass variables to the command line, including --dry-run or -d:
./radarr_tagarr3.py --dry-run -k "myAPIkey21384y01h" -n "https://radarr.com/myhost"

I've just downloaded v1.5 - it works beautifully.

Many thanks.

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