From bf1db64b82e86cf1f72c91c554514996acbbaf1b Mon Sep 17 00:00:00 2001 From: "Nicholas St. Germain" Date: Sat, 1 Dec 2018 20:33:33 -0600 Subject: [PATCH] migrated radarr --- .gitignore | 2 - Legacy/radarr.py | 171 ---------------- Varken/helpers.py | 426 ++++++++++++++++++++++------------------ Varken/iniparser.py | 12 +- Varken/radarr.py | 106 ++++++++++ Varken/sonarr.py | 2 - data/varken.example.ini | 5 + varken.py | 12 ++ 8 files changed, 371 insertions(+), 365 deletions(-) delete mode 100644 Legacy/radarr.py create mode 100644 Varken/radarr.py diff --git a/.gitignore b/.gitignore index a7e3e02..f238ac1 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,5 @@ __pycache__ GeoLite2-City.mmdb GeoLite2-City.tar.gz data/varken.ini -data/GeoLite2-City.mmdb -data/GeoLite2-City.tar.gz .idea/ Legacy/configuration.py diff --git a/Legacy/radarr.py b/Legacy/radarr.py deleted file mode 100644 index eea3499..0000000 --- a/Legacy/radarr.py +++ /dev/null @@ -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) diff --git a/Varken/helpers.py b/Varken/helpers.py index f84689b..434f2ee 100644 --- a/Varken/helpers.py +++ b/Varken/helpers.py @@ -25,7 +25,48 @@ class TVShow(NamedTuple): 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): + movie: dict = None series: dict = None episode: dict = None quality: dict = None @@ -54,6 +95,15 @@ class SonarrServer(NamedTuple): queue: bool = False 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): id: int = None @@ -82,193 +132,195 @@ class InfluxServer(NamedTuple): class TautulliStream(NamedTuple): - rating: str - transcode_width: str - labels: list - stream_bitrate: str - bandwidth: str - optimized_version: int - video_language: str - parent_rating_key: str - rating_key: str - platform_version: str - transcode_hw_decoding: int - thumb: str - title: str - video_codec_level: str - tagline: str - last_viewed_at: str - audio_sample_rate: str - user_rating: str - platform: str - collections: list - location: str - transcode_container: str - audio_channel_layout: str - local: str - stream_subtitle_format: str - stream_video_ref_frames: str - transcode_hw_encode_title: str - stream_container_decision: str - audience_rating: str - full_title: str - ip_address: str - subtitles: int - stream_subtitle_language: str - channel_stream: int - video_bitrate: str - is_allow_sync: int - stream_video_bitrate: str - summary: str - stream_audio_decision: str - aspect_ratio: str - audio_bitrate_mode: str - transcode_hw_decode_title: str - stream_audio_channel_layout: str - deleted_user: int - library_name: str - art: str - stream_video_resolution: str - video_profile: str - sort_title: str - stream_video_codec_level: str - stream_video_height: str - year: str - stream_duration: str - stream_audio_channels: str - video_language_code: str - transcode_key: str - transcode_throttled: int - container: str - stream_audio_bitrate: str - user: str - selected: int - product_version: str - subtitle_location: str - transcode_hw_requested: int - video_height: str - state: str - is_restricted: int - email: str - stream_container: str - transcode_speed: str - video_bit_depth: str - stream_audio_sample_rate: str - grandparent_title: str - studio: str - transcode_decision: str - video_width: str - bitrate: str - machine_id: str - originally_available_at: str - video_frame_rate: str - synced_version_profile: str - friendly_name: str - audio_profile: str - optimized_version_title: str - platform_name: str - stream_video_language: str - keep_history: int - stream_audio_codec: str - stream_video_codec: str - grandparent_thumb: str - synced_version: int - transcode_hw_decode: str - user_thumb: str - stream_video_width: str - height: str - stream_subtitle_decision: str - audio_codec: str - parent_title: str - guid: str - audio_language_code: str - transcode_video_codec: str - transcode_audio_codec: str - stream_video_decision: str - user_id: int - transcode_height: str - transcode_hw_full_pipeline: int - throttled: str - quality_profile: str - width: str - live: int - stream_subtitle_forced: int - media_type: str - video_resolution: str - stream_subtitle_location: str - do_notify: int - video_ref_frames: str - stream_subtitle_language_code: str - audio_channels: str - stream_audio_language_code: str - optimized_version_profile: str - relay: int - duration: str - rating_image: str - is_home_user: int - is_admin: int - ip_address_public: str - allow_guest: int - transcode_audio_channels: str - stream_audio_channel_layout_: str - media_index: str - stream_video_framerate: str - transcode_hw_encode: str - grandparent_rating_key: str - original_title: str - added_at: str - banner: str - bif_thumb: str - parent_media_index: str - live_uuid: str - audio_language: str - stream_audio_bitrate_mode: str - username: str - subtitle_decision: str - children_count: str - updated_at: str - player: str - subtitle_format: str - file: str - file_size: str - session_key: str - id: str - subtitle_container: str - genres: list - stream_video_language_code: str - indexes: int - video_decision: str - stream_audio_language: str - writers: list - actors: list - progress_percent: str - audio_decision: str - subtitle_forced: int - profile: str - product: str - view_offset: str - type: str - audience_rating_image: str - audio_bitrate: str - section_id: str - stream_subtitle_codec: str - subtitle_codec: str - video_codec: str - device: str - stream_video_bit_depth: str - video_framerate: str - transcode_hw_encoding: int - transcode_protocol: str - shared_libraries: list - stream_aspect_ratio: str - content_rating: str - session_id: str - directors: list - parent_thumb: str - subtitle_language_code: str - transcode_progress: int - subtitle_language: str - stream_subtitle_container: str + rating: str = None + transcode_width: str = None + labels: list = None + stream_bitrate: str = None + bandwidth: str = None + optimized_version: int = None + video_language: str = None + parent_rating_key: str = None + rating_key: str = None + platform_version: str = None + transcode_hw_decoding: int = None + thumb: str = None + title: str = None + video_codec_level: str = None + tagline: str = None + last_viewed_at: str = None + audio_sample_rate: str = None + user_rating: str = None + platform: str = None + collections: list = None + location: str = None + transcode_container: str = None + audio_channel_layout: str = None + local: str = None + stream_subtitle_format: str = None + stream_video_ref_frames: str = None + transcode_hw_encode_title: str = None + stream_container_decision: str = None + audience_rating: str = None + full_title: str = None + ip_address: str = None + subtitles: int = None + stream_subtitle_language: str = None + channel_stream: int = None + video_bitrate: str = None + is_allow_sync: int = None + stream_video_bitrate: str = None + summary: str = None + stream_audio_decision: str = None + aspect_ratio: str = None + audio_bitrate_mode: str = None + transcode_hw_decode_title: str = None + stream_audio_channel_layout: str = None + deleted_user: int = None + library_name: str = None + art: str = None + stream_video_resolution: str = None + video_profile: str = None + sort_title: str = None + stream_video_codec_level: str = None + stream_video_height: str = None + year: str = None + stream_duration: str = None + stream_audio_channels: str = None + video_language_code: str = None + transcode_key: str = None + transcode_throttled: int = None + container: str = None + stream_audio_bitrate: str = None + user: str = None + selected: int = None + product_version: str = None + subtitle_location: str = None + transcode_hw_requested: int = None + video_height: str = None + state: str = None + is_restricted: int = None + email: str = None + stream_container: str = None + transcode_speed: str = None + video_bit_depth: str = None + stream_audio_sample_rate: str = None + grandparent_title: str = None + studio: str = None + transcode_decision: str = None + video_width: str = None + bitrate: str = None + machine_id: str = None + originally_available_at: str = None + video_frame_rate: str = None + synced_version_profile: str = None + friendly_name: str = None + audio_profile: str = None + optimized_version_title: str = None + platform_name: str = None + stream_video_language: str = None + keep_history: int = None + stream_audio_codec: str = None + stream_video_codec: str = None + grandparent_thumb: str = None + synced_version: int = None + transcode_hw_decode: str = None + user_thumb: str = None + stream_video_width: str = None + height: str = None + stream_subtitle_decision: str = None + audio_codec: str = None + parent_title: str = None + guid: str = None + audio_language_code: str = None + transcode_video_codec: str = None + transcode_audio_codec: str = None + stream_video_decision: str = None + user_id: int = None + transcode_height: str = None + transcode_hw_full_pipeline: int = None + throttled: str = None + quality_profile: str = None + width: str = None + live: int = None + stream_subtitle_forced: int = None + media_type: str = None + video_resolution: str = None + stream_subtitle_location: str = None + do_notify: int = None + video_ref_frames: str = None + stream_subtitle_language_code: str = None + audio_channels: str = None + stream_audio_language_code: str = None + optimized_version_profile: str = None + relay: int = None + duration: str = None + rating_image: str = None + is_home_user: int = None + is_admin: int = None + ip_address_public: str = None + allow_guest: int = None + transcode_audio_channels: str = None + stream_audio_channel_layout_: str = None + media_index: str = None + stream_video_framerate: str = None + transcode_hw_encode: str = None + grandparent_rating_key: str = None + original_title: str = None + added_at: str = None + banner: str = None + bif_thumb: str = None + parent_media_index: str = None + live_uuid: str = None + audio_language: str = None + stream_audio_bitrate_mode: str = None + username: str = None + subtitle_decision: str = None + children_count: str = None + updated_at: str = None + player: str = None + subtitle_format: str = None + file: str = None + file_size: str = None + session_key: str = None + id: str = None + subtitle_container: str = None + genres: list = None + stream_video_language_code: str = None + indexes: int = None + video_decision: str = None + stream_audio_language: str = None + writers: list = None + actors: list = None + progress_percent: str = None + audio_decision: str = None + subtitle_forced: int = None + profile: str = None + product: str = None + view_offset: str = None + type: str = None + audience_rating_image: str = None + audio_bitrate: str = None + section_id: str = None + stream_subtitle_codec: str = None + subtitle_codec: str = None + video_codec: str = None + device: str = None + stream_video_bit_depth: str = None + video_framerate: str = None + transcode_hw_encoding: int = None + transcode_protocol: str = None + shared_libraries: list = None + stream_aspect_ratio: str = None + content_rating: str = None + session_id: str = None + directors: list = None + parent_thumb: str = None + subtitle_language_code: str = None + transcode_progress: int = None + subtitle_language: str = None + stream_subtitle_container: str = None + _cache_time: int = None + def geoip_download(): tar_dbfile = abspath(join('.', 'data', 'GeoLite2-City.tar.gz')) diff --git a/Varken/iniparser.py b/Varken/iniparser.py index 7793161..89d56fc 100644 --- a/Varken/iniparser.py +++ b/Varken/iniparser.py @@ -1,7 +1,7 @@ import sys import configparser 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): @@ -81,7 +81,7 @@ class INIParser(object): except ValueError: self.radarr_enabled = True - if self.sonarr_enabled: + if self.radarr_enabled: sids = self.config.get('global', 'radarr_server_ids').strip(' ').split(',') for server_id in sids: @@ -90,8 +90,14 @@ class INIParser(object): apikey = self.config.get(radarr_section, 'apikey') scheme = 'https://' if self.config.getboolean(radarr_section, 'ssl') else 'http://' 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 try: diff --git a/Varken/radarr.py b/Varken/radarr.py new file mode 100644 index 0000000..33f7c0b --- /dev/null +++ b/Varken/radarr.py @@ -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) diff --git a/Varken/sonarr.py b/Varken/sonarr.py index 79cab8a..9ee89c2 100644 --- a/Varken/sonarr.py +++ b/Varken/sonarr.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# Do not edit this script. Edit configuration.py import requests from influxdb import InfluxDBClient from datetime import datetime, timezone, date, timedelta diff --git a/data/varken.example.ini b/data/varken.example.ini index aaa3bb6..0ebd511 100644 --- a/data/varken.example.ini +++ b/data/varken.example.ini @@ -58,6 +58,11 @@ url = radarr1.domain.tld apikey = xxxxxxxxxxxxxxxx ssl = false verify_ssl = true +queue = true +queue_run_seconds = 300 +get_missing = true +get_missing_available = true +get_missing_run_seconds = 300 [radarr-2] url = radarr2.domain.tld diff --git a/varken.py b/varken.py index 8be5b2c..482e788 100644 --- a/varken.py +++ b/varken.py @@ -5,6 +5,7 @@ from time import sleep from Varken.iniparser import INIParser from Varken.sonarr import SonarrAPI from Varken.tautulli import TautulliAPI +from Varken.radarr import RadarrAPI def threaded(job, days=None): @@ -37,6 +38,17 @@ if __name__ == "__main__": if server.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: schedule.run_pending() sleep(1)