migrated radarr

This commit is contained in:
Nicholas St. Germain 2018-12-01 20:33:33 -06:00
parent d1079ab949
commit bf1db64b82
8 changed files with 371 additions and 365 deletions

2
.gitignore vendored
View file

@ -9,7 +9,5 @@ __pycache__
GeoLite2-City.mmdb GeoLite2-City.mmdb
GeoLite2-City.tar.gz GeoLite2-City.tar.gz
data/varken.ini data/varken.ini
data/GeoLite2-City.mmdb
data/GeoLite2-City.tar.gz
.idea/ .idea/
Legacy/configuration.py Legacy/configuration.py

View file

@ -1,171 +0,0 @@
# Do not edit this script. Edit configuration.py
import sys
import requests
from datetime import datetime, timezone
from influxdb import InfluxDBClient
import argparse
from argparse import RawTextHelpFormatter
from Legacy import configuration
def now_iso():
now_iso = datetime.now(timezone.utc).astimezone().isoformat()
return now_iso
def influx_sender(influx_payload):
influx = InfluxDBClient(configuration.influxdb_url, configuration.influxdb_port, configuration.influxdb_username,
configuration.influxdb_password, configuration.radarr_influxdb_db_name)
influx.write_points(influx_payload)
def get_missing_movies():
# Set the time here so we have one timestamp to work with
now = now_iso()
missing = []
influx_payload = []
for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list:
headers = {'X-Api-Key': radarr_api_key}
get_movies = requests.get('{}/api/movie'.format(radarr_url), headers=headers).json()
movies = {d['tmdbId']: d for d in get_movies}
for movie in movies.keys():
if not movies[movie]['downloaded']:
movie_name = ('{} ({})'.format(movies[movie]['title'], movies[movie]['year']))
missing.append((movie_name, movies[movie]['tmdbId']))
for movie, id in missing:
influx_payload.append(
{
"measurement": "Radarr",
"tags": {
"type": "Missing",
"tmdbId": id,
"server": server_id
},
"time": now,
"fields": {
"name": movie
}
}
)
# Empty missing or else things get foo bared
missing = []
return influx_payload
def get_missing_avl():
# Set the time here so we have one timestamp to work with
now = now_iso()
missing = []
influx_payload = []
for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list:
headers = {'X-Api-Key': radarr_api_key}
get_movies = requests.get('{}/api/movie'.format(radarr_url), headers=headers).json()
movies = {d['tmdbId']: d for d in get_movies}
for movie in movies.keys():
if not movies[movie]['downloaded']:
if movies[movie]['isAvailable'] is True:
movie_name = ('{} ({})'.format(movies[movie]['title'], movies[movie]['year']))
missing.append((movie_name, movies[movie]['tmdbId']))
for movie, id in missing:
influx_payload.append(
{
"measurement": "Radarr",
"tags": {
"type": "Missing_Available",
"tmdbId": id,
"server": server_id
},
"time": now,
"fields": {
"name": movie,
}
}
)
# Empty missing or else things get foo bared
missing = []
return influx_payload
def get_queue_movies():
# Set the time here so we have one timestamp to work with
now = now_iso()
influx_payload = []
queue = []
for radarr_url, radarr_api_key, server_id in configuration.radarr_server_list:
headers = {'X-Api-Key': radarr_api_key}
get_movies = requests.get('{}/api/queue'.format(radarr_url), headers=headers).json()
queue_movies = {d['id']: d for d in get_movies}
for movie in queue_movies.keys():
name = '{} ({})'.format(queue_movies[movie]['movie']['title'], queue_movies[movie]['movie']['year'])
quality = (queue_movies[movie]['quality']['quality']['name'])
protocol = (queue_movies[movie]['protocol'].upper())
if protocol == 'USENET':
protocol_id = 1
else:
protocol_id = 0
queue.append((name, queue_movies[movie]['id']))
for movie, id in queue:
influx_payload.append(
{
"measurement": "Radarr",
"tags": {
"type": "Queue",
"tmdbId": id,
"server": server_id
},
"time": now,
"fields": {
"name": movie,
"quality": quality,
"protocol": protocol,
"protocol_id": protocol_id
}
}
)
# Empty queue or else things get foo bared
queue = []
return influx_payload
if __name__ == "__main__":
parser = argparse.ArgumentParser(prog='Radarr stats operations',
description='Script to aid in data gathering from Radarr', formatter_class=RawTextHelpFormatter)
parser.add_argument("--missing", action='store_true',
help='Get missing movies')
parser.add_argument("--missing_avl", action='store_true',
help='Get missing yet available movies')
parser.add_argument("--queue", action='store_true',
help='Get movies in queue')
opts = parser.parse_args()
if opts.missing:
influx_sender(get_missing_movies())
elif opts.missing_avl:
influx_sender(get_missing_avl())
elif opts.queue:
influx_sender(get_queue_movies())
elif len(sys.argv) == 1:
parser.print_help(sys.stderr)
sys.exit(1)

View file

@ -25,7 +25,48 @@ class TVShow(NamedTuple):
id: int = None id: int = None
class Movie(NamedTuple):
title: str = None
alternativeTitles: list = None
secondaryYearSourceId: int = None
sortTitle: str = None
sizeOnDisk: int = None
status: str = None
overview: str = None
inCinemas: str = None
images: list = None
downloaded: bool = None
year: int = None
secondaryYear: str = None
hasFile: bool = None
youTubeTrailerId: str = None
studio: str = None
path: str = None
profileId: int = None
pathState: str = None
monitored: bool = None
minimumAvailability: str = None
isAvailable: bool = None
folderName: str = None
runtime: int = None
lastInfoSync: str = None
cleanTitle: str = None
imdbId: str = None
tmdbId: int = None
titleSlug: str = None
genres: list = None
tags: list = None
added: str = None
ratings: dict = None
movieFile: dict = None
qualityProfileId: int = None
physicalRelease: str = None
physicalReleaseNote: str = None
website: str = None
id: int = None
class Queue(NamedTuple): class Queue(NamedTuple):
movie: dict = None
series: dict = None series: dict = None
episode: dict = None episode: dict = None
quality: dict = None quality: dict = None
@ -54,6 +95,15 @@ class SonarrServer(NamedTuple):
queue: bool = False queue: bool = False
queue_run_seconds: int = 1 queue_run_seconds: int = 1
class RadarrServer(NamedTuple):
id: int = None
url: str = None
api_key: str = None
verify_ssl: bool = False
queue: bool = False
queue_run_seconds: int = 1
get_missing: bool = False
get_missing_run_seconds: int = 30
class Server(NamedTuple): class Server(NamedTuple):
id: int = None id: int = None
@ -82,193 +132,195 @@ class InfluxServer(NamedTuple):
class TautulliStream(NamedTuple): class TautulliStream(NamedTuple):
rating: str rating: str = None
transcode_width: str transcode_width: str = None
labels: list labels: list = None
stream_bitrate: str stream_bitrate: str = None
bandwidth: str bandwidth: str = None
optimized_version: int optimized_version: int = None
video_language: str video_language: str = None
parent_rating_key: str parent_rating_key: str = None
rating_key: str rating_key: str = None
platform_version: str platform_version: str = None
transcode_hw_decoding: int transcode_hw_decoding: int = None
thumb: str thumb: str = None
title: str title: str = None
video_codec_level: str video_codec_level: str = None
tagline: str tagline: str = None
last_viewed_at: str last_viewed_at: str = None
audio_sample_rate: str audio_sample_rate: str = None
user_rating: str user_rating: str = None
platform: str platform: str = None
collections: list collections: list = None
location: str location: str = None
transcode_container: str transcode_container: str = None
audio_channel_layout: str audio_channel_layout: str = None
local: str local: str = None
stream_subtitle_format: str stream_subtitle_format: str = None
stream_video_ref_frames: str stream_video_ref_frames: str = None
transcode_hw_encode_title: str transcode_hw_encode_title: str = None
stream_container_decision: str stream_container_decision: str = None
audience_rating: str audience_rating: str = None
full_title: str full_title: str = None
ip_address: str ip_address: str = None
subtitles: int subtitles: int = None
stream_subtitle_language: str stream_subtitle_language: str = None
channel_stream: int channel_stream: int = None
video_bitrate: str video_bitrate: str = None
is_allow_sync: int is_allow_sync: int = None
stream_video_bitrate: str stream_video_bitrate: str = None
summary: str summary: str = None
stream_audio_decision: str stream_audio_decision: str = None
aspect_ratio: str aspect_ratio: str = None
audio_bitrate_mode: str audio_bitrate_mode: str = None
transcode_hw_decode_title: str transcode_hw_decode_title: str = None
stream_audio_channel_layout: str stream_audio_channel_layout: str = None
deleted_user: int deleted_user: int = None
library_name: str library_name: str = None
art: str art: str = None
stream_video_resolution: str stream_video_resolution: str = None
video_profile: str video_profile: str = None
sort_title: str sort_title: str = None
stream_video_codec_level: str stream_video_codec_level: str = None
stream_video_height: str stream_video_height: str = None
year: str year: str = None
stream_duration: str stream_duration: str = None
stream_audio_channels: str stream_audio_channels: str = None
video_language_code: str video_language_code: str = None
transcode_key: str transcode_key: str = None
transcode_throttled: int transcode_throttled: int = None
container: str container: str = None
stream_audio_bitrate: str stream_audio_bitrate: str = None
user: str user: str = None
selected: int selected: int = None
product_version: str product_version: str = None
subtitle_location: str subtitle_location: str = None
transcode_hw_requested: int transcode_hw_requested: int = None
video_height: str video_height: str = None
state: str state: str = None
is_restricted: int is_restricted: int = None
email: str email: str = None
stream_container: str stream_container: str = None
transcode_speed: str transcode_speed: str = None
video_bit_depth: str video_bit_depth: str = None
stream_audio_sample_rate: str stream_audio_sample_rate: str = None
grandparent_title: str grandparent_title: str = None
studio: str studio: str = None
transcode_decision: str transcode_decision: str = None
video_width: str video_width: str = None
bitrate: str bitrate: str = None
machine_id: str machine_id: str = None
originally_available_at: str originally_available_at: str = None
video_frame_rate: str video_frame_rate: str = None
synced_version_profile: str synced_version_profile: str = None
friendly_name: str friendly_name: str = None
audio_profile: str audio_profile: str = None
optimized_version_title: str optimized_version_title: str = None
platform_name: str platform_name: str = None
stream_video_language: str stream_video_language: str = None
keep_history: int keep_history: int = None
stream_audio_codec: str stream_audio_codec: str = None
stream_video_codec: str stream_video_codec: str = None
grandparent_thumb: str grandparent_thumb: str = None
synced_version: int synced_version: int = None
transcode_hw_decode: str transcode_hw_decode: str = None
user_thumb: str user_thumb: str = None
stream_video_width: str stream_video_width: str = None
height: str height: str = None
stream_subtitle_decision: str stream_subtitle_decision: str = None
audio_codec: str audio_codec: str = None
parent_title: str parent_title: str = None
guid: str guid: str = None
audio_language_code: str audio_language_code: str = None
transcode_video_codec: str transcode_video_codec: str = None
transcode_audio_codec: str transcode_audio_codec: str = None
stream_video_decision: str stream_video_decision: str = None
user_id: int user_id: int = None
transcode_height: str transcode_height: str = None
transcode_hw_full_pipeline: int transcode_hw_full_pipeline: int = None
throttled: str throttled: str = None
quality_profile: str quality_profile: str = None
width: str width: str = None
live: int live: int = None
stream_subtitle_forced: int stream_subtitle_forced: int = None
media_type: str media_type: str = None
video_resolution: str video_resolution: str = None
stream_subtitle_location: str stream_subtitle_location: str = None
do_notify: int do_notify: int = None
video_ref_frames: str video_ref_frames: str = None
stream_subtitle_language_code: str stream_subtitle_language_code: str = None
audio_channels: str audio_channels: str = None
stream_audio_language_code: str stream_audio_language_code: str = None
optimized_version_profile: str optimized_version_profile: str = None
relay: int relay: int = None
duration: str duration: str = None
rating_image: str rating_image: str = None
is_home_user: int is_home_user: int = None
is_admin: int is_admin: int = None
ip_address_public: str ip_address_public: str = None
allow_guest: int allow_guest: int = None
transcode_audio_channels: str transcode_audio_channels: str = None
stream_audio_channel_layout_: str stream_audio_channel_layout_: str = None
media_index: str media_index: str = None
stream_video_framerate: str stream_video_framerate: str = None
transcode_hw_encode: str transcode_hw_encode: str = None
grandparent_rating_key: str grandparent_rating_key: str = None
original_title: str original_title: str = None
added_at: str added_at: str = None
banner: str banner: str = None
bif_thumb: str bif_thumb: str = None
parent_media_index: str parent_media_index: str = None
live_uuid: str live_uuid: str = None
audio_language: str audio_language: str = None
stream_audio_bitrate_mode: str stream_audio_bitrate_mode: str = None
username: str username: str = None
subtitle_decision: str subtitle_decision: str = None
children_count: str children_count: str = None
updated_at: str updated_at: str = None
player: str player: str = None
subtitle_format: str subtitle_format: str = None
file: str file: str = None
file_size: str file_size: str = None
session_key: str session_key: str = None
id: str id: str = None
subtitle_container: str subtitle_container: str = None
genres: list genres: list = None
stream_video_language_code: str stream_video_language_code: str = None
indexes: int indexes: int = None
video_decision: str video_decision: str = None
stream_audio_language: str stream_audio_language: str = None
writers: list writers: list = None
actors: list actors: list = None
progress_percent: str progress_percent: str = None
audio_decision: str audio_decision: str = None
subtitle_forced: int subtitle_forced: int = None
profile: str profile: str = None
product: str product: str = None
view_offset: str view_offset: str = None
type: str type: str = None
audience_rating_image: str audience_rating_image: str = None
audio_bitrate: str audio_bitrate: str = None
section_id: str section_id: str = None
stream_subtitle_codec: str stream_subtitle_codec: str = None
subtitle_codec: str subtitle_codec: str = None
video_codec: str video_codec: str = None
device: str device: str = None
stream_video_bit_depth: str stream_video_bit_depth: str = None
video_framerate: str video_framerate: str = None
transcode_hw_encoding: int transcode_hw_encoding: int = None
transcode_protocol: str transcode_protocol: str = None
shared_libraries: list shared_libraries: list = None
stream_aspect_ratio: str stream_aspect_ratio: str = None
content_rating: str content_rating: str = None
session_id: str session_id: str = None
directors: list directors: list = None
parent_thumb: str parent_thumb: str = None
subtitle_language_code: str subtitle_language_code: str = None
transcode_progress: int transcode_progress: int = None
subtitle_language: str subtitle_language: str = None
stream_subtitle_container: str stream_subtitle_container: str = None
_cache_time: int = None
def geoip_download(): def geoip_download():
tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz'))

View file

@ -1,7 +1,7 @@
import sys import sys
import configparser import configparser
from os.path import abspath, join from os.path import abspath, join
from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer from Varken.helpers import Server, TautulliServer, SonarrServer, InfluxServer, RadarrServer
class INIParser(object): class INIParser(object):
@ -81,7 +81,7 @@ class INIParser(object):
except ValueError: except ValueError:
self.radarr_enabled = True self.radarr_enabled = True
if self.sonarr_enabled: if self.radarr_enabled:
sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',')
for server_id in sids: for server_id in sids:
@ -90,8 +90,14 @@ class INIParser(object):
apikey = self.config.get(radarr_section, 'apikey') apikey = self.config.get(radarr_section, 'apikey')
scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://'
verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl') verify_ssl = self.config.getboolean(radarr_section, 'verify_ssl')
queue = self.config.getboolean(radarr_section, 'queue')
queue_run_seconds = self.config.getint(radarr_section, 'queue_run_seconds')
get_missing = self.config.getboolean(radarr_section, 'get_missing')
get_missing_run_seconds = self.config.getint(radarr_section, 'get_missing_run_seconds')
self.radarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl)) server = RadarrServer(server_id, scheme + url, apikey, verify_ssl, queue, queue_run_seconds,
get_missing, get_missing_run_seconds)
self.radarr_servers.append(server)
# Parse Tautulli options # Parse Tautulli options
try: try:

106
Varken/radarr.py Normal file
View file

@ -0,0 +1,106 @@
import requests
from datetime import datetime, timezone
from influxdb import InfluxDBClient
from Varken.logger import logging
from Varken.helpers import Movie, Queue
class RadarrAPI(object):
def __init__(self, servers, influx_server):
self.now = datetime.now(timezone.utc).astimezone().isoformat()
self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username,
influx_server.password, 'plex2')
self.servers = servers
# Create session to reduce server web thread load, and globally define pageSize for all requests
self.session = requests.Session()
def influx_push(self, payload):
# TODO: error handling for failed connection
self.influx.write_points(payload)
@logging
def get_missing(self, notimplemented):
endpoint = '/api/movie'
self.now = datetime.now(timezone.utc).astimezone().isoformat()
influx_payload = []
for server in self.servers:
missing = []
headers = {'X-Api-Key': server.api_key}
get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json()
movies = [Movie(**movie) for movie in get]
for movie in movies:
if server.get_missing:
if not movie.downloaded and movie.isAvailable:
ma = True
else:
ma = False
movie_name = '{} ({})'.format(movie.title, movie.year)
missing.append((movie_name, ma, movie.tmdbId))
for title, ma, mid in missing:
influx_payload.append(
{
"measurement": "Radarr",
"tags": {
"Missing": True,
"Missing_Available": ma,
"tmdbId": mid,
"server": server.id
},
"time": self.now,
"fields": {
"name": title
}
}
)
self.influx_push(influx_payload)
@logging
def get_queue(self, notimplemented):
endpoint = '/api/queue'
self.now = datetime.now(timezone.utc).astimezone().isoformat()
influx_payload = []
for server in self.servers:
queue = []
headers = {'X-Api-Key': server.api_key}
get = self.session.get(server.url + endpoint, headers=headers, verify=server.verify_ssl).json()
for movie in get:
movie['movie'] = Movie(**movie['movie'])
download_queue = [Queue(**movie) for movie in get]
for queue_item in download_queue:
name = '{} ({})'.format(queue_item.movie.title, queue_item.movie.year)
if queue_item.protocol.upper() == 'USENET':
protocol_id = 1
else:
protocol_id = 0
queue.append((name, queue_item.quality['quality']['name'], queue_item.protocol.upper(),
protocol_id, queue_item.id))
for movie, quality, protocol, protocol_id, qid in queue:
influx_payload.append(
{
"measurement": "Radarr",
"tags": {
"type": "Queue",
"tmdbId": qid,
"server": server.id
},
"time": self.now,
"fields": {
"name": movie,
"quality": quality,
"protocol": protocol,
"protocol_id": protocol_id
}
}
)
self.influx_push(influx_payload)

View file

@ -1,5 +1,3 @@
#!/usr/bin/env python3
# Do not edit this script. Edit configuration.py
import requests import requests
from influxdb import InfluxDBClient from influxdb import InfluxDBClient
from datetime import datetime, timezone, date, timedelta from datetime import datetime, timezone, date, timedelta

View file

@ -58,6 +58,11 @@ url = radarr1.domain.tld
apikey = xxxxxxxxxxxxxxxx apikey = xxxxxxxxxxxxxxxx
ssl = false ssl = false
verify_ssl = true verify_ssl = true
queue = true
queue_run_seconds = 300
get_missing = true
get_missing_available = true
get_missing_run_seconds = 300
[radarr-2] [radarr-2]
url = radarr2.domain.tld url = radarr2.domain.tld

View file

@ -5,6 +5,7 @@ from time import sleep
from Varken.iniparser import INIParser from Varken.iniparser import INIParser
from Varken.sonarr import SonarrAPI from Varken.sonarr import SonarrAPI
from Varken.tautulli import TautulliAPI from Varken.tautulli import TautulliAPI
from Varken.radarr import RadarrAPI
def threaded(job, days=None): def threaded(job, days=None):
@ -37,6 +38,17 @@ if __name__ == "__main__":
if server.get_sessions: if server.get_sessions:
schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions) schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions)
if CONFIG.radarr_enabled:
RADARR = RadarrAPI(CONFIG.radarr_servers, CONFIG.influx_server)
for server in CONFIG.radarr_servers:
if any([server.get_missing, server.get_missing_available]):
schedule.every(server.get_missing_run_seconds).seconds.do(threaded, RADARR.get_missing)
if server.queue:
schedule.every(server.queue_run_seconds).seconds.do(threaded, RADARR.get_queue)
while True: while True:
schedule.run_pending() schedule.run_pending()
sleep(1) sleep(1)