Migrated tautulli.py and allowed for multiple servers
This commit is contained in:
parent
32b965edf8
commit
7638cd937e
8 changed files with 439 additions and 206 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,4 +12,4 @@ GeoLite2-City.mmdb
|
||||||
GeoLite2-City.tar.gz
|
GeoLite2-City.tar.gz
|
||||||
.idea/
|
.idea/
|
||||||
.idea/*
|
.idea/*
|
||||||
Varken/varken.ini
|
varken.ini
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
import os
|
|
||||||
import tarfile
|
|
||||||
import urllib.request
|
|
||||||
import time
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
import geoip2.database
|
|
||||||
from influxdb import InfluxDBClient
|
|
||||||
import requests
|
|
||||||
from Varken import configuration
|
|
||||||
|
|
||||||
CURRENT_TIME = datetime.now(timezone.utc).astimezone().isoformat()
|
|
||||||
|
|
||||||
PAYLOAD = {'apikey': configuration.tautulli_api_key, 'cmd': 'get_activity'}
|
|
||||||
|
|
||||||
ACTIVITY = requests.get('{}/api/v2'.format(configuration.tautulli_url),
|
|
||||||
params=PAYLOAD).json()['response']['data']
|
|
||||||
|
|
||||||
SESSIONS = {d['session_id']: d for d in ACTIVITY['sessions']}
|
|
||||||
|
|
||||||
TAR_DBFILE = '{}/GeoLite2-City.tar.gz'.format(os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
|
|
||||||
DBFILE = '{}/GeoLite2-City.mmdb'.format(os.path.dirname(os.path.realpath(__file__)))
|
|
||||||
|
|
||||||
NOW = time.time()
|
|
||||||
|
|
||||||
DB_AGE = NOW - (86400 * 35)
|
|
||||||
|
|
||||||
#remove the running db file if it is older than 35 days
|
|
||||||
try:
|
|
||||||
t = os.stat(DBFILE)
|
|
||||||
c = t.st_ctime
|
|
||||||
if c < DB_AGE:
|
|
||||||
os.remove(DBFILE)
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def geo_lookup(ipaddress):
|
|
||||||
"""Lookup an IP using the local GeoLite2 DB"""
|
|
||||||
if not os.path.isfile(DBFILE):
|
|
||||||
urllib.request.urlretrieve(
|
|
||||||
'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz',
|
|
||||||
TAR_DBFILE)
|
|
||||||
|
|
||||||
tar = tarfile.open(TAR_DBFILE, "r:gz")
|
|
||||||
for files in tar.getmembers():
|
|
||||||
if 'GeoLite2-City.mmdb' in files.name:
|
|
||||||
files.name = os.path.basename(files.name)
|
|
||||||
tar.extract(files, '{}/'.format(os.path.dirname(os.path.realpath(__file__))))
|
|
||||||
|
|
||||||
reader = geoip2.database.Reader(DBFILE)
|
|
||||||
|
|
||||||
return reader.city(ipaddress)
|
|
||||||
|
|
||||||
|
|
||||||
INFLUX_PAYLOAD = [
|
|
||||||
{
|
|
||||||
"measurement": "Tautulli",
|
|
||||||
"tags": {
|
|
||||||
"type": "stream_count"
|
|
||||||
},
|
|
||||||
"time": CURRENT_TIME,
|
|
||||||
"fields": {
|
|
||||||
"current_streams": int(ACTIVITY['stream_count']),
|
|
||||||
"transcode_streams": int(ACTIVITY['stream_count_transcode']),
|
|
||||||
"direct_play_streams": int(ACTIVITY['stream_count_direct_play']),
|
|
||||||
"direct_streams": int(ACTIVITY['stream_count_direct_stream'])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
for session in SESSIONS.keys():
|
|
||||||
try:
|
|
||||||
geodata = geo_lookup(SESSIONS[session]['ip_address_public'])
|
|
||||||
except (ValueError, geoip2.errors.AddressNotFoundError):
|
|
||||||
if configuration.tautulli_failback_ip:
|
|
||||||
geodata = geo_lookup(configuration.tautulli_failback_ip)
|
|
||||||
else:
|
|
||||||
geodata = geo_lookup(requests.get('http://ip.42.pl/raw').text)
|
|
||||||
|
|
||||||
latitude = geodata.location.latitude
|
|
||||||
|
|
||||||
if not geodata.location.latitude:
|
|
||||||
latitude = 37.234332396
|
|
||||||
else:
|
|
||||||
latitude = geodata.location.latitude
|
|
||||||
|
|
||||||
if not geodata.location.longitude:
|
|
||||||
longitude = -115.80666344
|
|
||||||
else:
|
|
||||||
longitude = geodata.location.longitude
|
|
||||||
|
|
||||||
decision = SESSIONS[session]['transcode_decision']
|
|
||||||
|
|
||||||
if decision == 'copy':
|
|
||||||
decision = 'direct stream'
|
|
||||||
|
|
||||||
video_decision = SESSIONS[session]['stream_video_decision']
|
|
||||||
|
|
||||||
if video_decision == 'copy':
|
|
||||||
video_decision = 'direct stream'
|
|
||||||
|
|
||||||
elif video_decision == '':
|
|
||||||
video_decision = 'Music'
|
|
||||||
|
|
||||||
quality = SESSIONS[session]['stream_video_resolution']
|
|
||||||
|
|
||||||
|
|
||||||
# If the video resolution is empty. Asssume it's an audio stream
|
|
||||||
# and use the container for music
|
|
||||||
if not quality:
|
|
||||||
quality = SESSIONS[session]['container'].upper()
|
|
||||||
|
|
||||||
elif quality in ('SD', 'sd'):
|
|
||||||
quality = SESSIONS[session]['stream_video_resolution'].upper()
|
|
||||||
|
|
||||||
elif quality in '4k':
|
|
||||||
quality = SESSIONS[session]['stream_video_resolution'].upper()
|
|
||||||
|
|
||||||
else:
|
|
||||||
quality = '{}p'.format(SESSIONS[session]['stream_video_resolution'])
|
|
||||||
|
|
||||||
|
|
||||||
# Translate player_state to integers so we can colorize the table
|
|
||||||
player_state = SESSIONS[session]['state'].lower()
|
|
||||||
|
|
||||||
if player_state == 'playing':
|
|
||||||
player_state = 0
|
|
||||||
|
|
||||||
elif player_state == 'paused':
|
|
||||||
player_state = 1
|
|
||||||
|
|
||||||
elif player_state == 'buffering':
|
|
||||||
player_state = 3
|
|
||||||
|
|
||||||
|
|
||||||
INFLUX_PAYLOAD.append(
|
|
||||||
{
|
|
||||||
"measurement": "Tautulli",
|
|
||||||
"tags": {
|
|
||||||
"type": "Session",
|
|
||||||
"session_id": SESSIONS[session]['session_id'],
|
|
||||||
"name": SESSIONS[session]['friendly_name'],
|
|
||||||
"title": SESSIONS[session]['full_title'],
|
|
||||||
"platform": SESSIONS[session]['platform'],
|
|
||||||
"product_version": SESSIONS[session]['product_version'],
|
|
||||||
"quality": quality,
|
|
||||||
"video_decision": video_decision.title(),
|
|
||||||
"transcode_decision": decision.title(),
|
|
||||||
"media_type": SESSIONS[session]['media_type'].title(),
|
|
||||||
"audio_codec": SESSIONS[session]['audio_codec'].upper(),
|
|
||||||
"audio_profile": SESSIONS[session]['audio_profile'].upper(),
|
|
||||||
"stream_audio_codec": SESSIONS[session]['stream_audio_codec'].upper(),
|
|
||||||
"quality_profile": SESSIONS[session]['quality_profile'],
|
|
||||||
"progress_percent": SESSIONS[session]['progress_percent'],
|
|
||||||
"region_code": geodata.subdivisions.most_specific.iso_code,
|
|
||||||
"location": geodata.city.name,
|
|
||||||
"full_location": '{} - {}'.format(geodata.subdivisions.most_specific.name,
|
|
||||||
geodata.city.name),
|
|
||||||
"latitude": latitude,
|
|
||||||
"longitude": longitude,
|
|
||||||
"player_state": player_state,
|
|
||||||
"device_type": SESSIONS[session]['platform']
|
|
||||||
},
|
|
||||||
"time": CURRENT_TIME,
|
|
||||||
"fields": {
|
|
||||||
"session_id": SESSIONS[session]['session_id'],
|
|
||||||
"session_key": SESSIONS[session]['session_key']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
INFLUX_SENDER = InfluxDBClient(configuration.influxdb_url,
|
|
||||||
configuration.influxdb_port,
|
|
||||||
configuration.influxdb_username,
|
|
||||||
configuration.influxdb_password,
|
|
||||||
configuration.tautulli_influxdb_db_name)
|
|
||||||
|
|
||||||
INFLUX_SENDER.write_points(INFLUX_PAYLOAD)
|
|
|
@ -1,4 +1,10 @@
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import tarfile
|
||||||
|
import geoip2.database
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
from os.path import abspath, join
|
||||||
|
from urllib.request import urlretrieve
|
||||||
|
|
||||||
|
|
||||||
class TVShow(NamedTuple):
|
class TVShow(NamedTuple):
|
||||||
|
@ -35,17 +41,19 @@ class Queue(NamedTuple):
|
||||||
protocol: str = None
|
protocol: str = None
|
||||||
id: int = None
|
id: int = None
|
||||||
|
|
||||||
|
|
||||||
class SonarrServer(NamedTuple):
|
class SonarrServer(NamedTuple):
|
||||||
id: int = None
|
id: int = None
|
||||||
url: str = None
|
url: str = None
|
||||||
api_key: str = None
|
api_key: str = None
|
||||||
verify_ssl: bool = False
|
verify_ssl: bool = False
|
||||||
missing_days: int = 0
|
missing_days: int = 0
|
||||||
missing_days_run_minutes: int = 30
|
missing_days_run_seconds: int = 30
|
||||||
future_days: int = 0
|
future_days: int = 0
|
||||||
future_days_run_minutes: int = 30
|
future_days_run_seconds: int = 30
|
||||||
queue: bool = False
|
queue: bool = False
|
||||||
queue_run_minutes: int = 1
|
queue_run_seconds: int = 1
|
||||||
|
|
||||||
|
|
||||||
class Server(NamedTuple):
|
class Server(NamedTuple):
|
||||||
id: int = None
|
id: int = None
|
||||||
|
@ -55,14 +63,238 @@ class Server(NamedTuple):
|
||||||
|
|
||||||
|
|
||||||
class TautulliServer(NamedTuple):
|
class TautulliServer(NamedTuple):
|
||||||
|
id: int = None
|
||||||
url: str = None
|
url: str = None
|
||||||
fallback_ip: str = None
|
fallback_ip: str = None
|
||||||
apikey: str = None
|
apikey: str = None
|
||||||
verify_ssl: bool = None
|
verify_ssl: bool = None
|
||||||
influx_db: str = None
|
get_activity: bool = False
|
||||||
|
get_activity_run_seconds: int = 30
|
||||||
|
get_sessions: bool = False
|
||||||
|
get_sessions_run_seconds: int = 30
|
||||||
|
|
||||||
|
|
||||||
class InfluxServer(NamedTuple):
|
class InfluxServer(NamedTuple):
|
||||||
url: str = 'localhost'
|
url: str = 'localhost'
|
||||||
port: int = 8086
|
port: int = 8086
|
||||||
username: str = 'root'
|
username: str = 'root'
|
||||||
password: str = 'root'
|
password: str = 'root'
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def geoip_download():
|
||||||
|
tar_dbfile = abspath(join('..', 'data', 'GeoLite2-City.tar.gz'))
|
||||||
|
url = 'http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz'
|
||||||
|
urlretrieve(url, tar_dbfile)
|
||||||
|
tar = tarfile.open(tar_dbfile, "r:gz")
|
||||||
|
for files in tar.getmembers():
|
||||||
|
if 'GeoLite2-City.mmdb' in files.name:
|
||||||
|
files.name = os.path.basename(files.name)
|
||||||
|
tar.extract(files, '{}/'.format(os.path.dirname(os.path.realpath(__file__))))
|
||||||
|
os.remove(tar_dbfile)
|
||||||
|
|
||||||
|
def geo_lookup(ipaddress):
|
||||||
|
|
||||||
|
dbfile = abspath(join('..', 'data', 'GeoLite2-City.mmdb'))
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbinfo = os.stat(dbfile)
|
||||||
|
db_age = now - dbinfo.st_ctime
|
||||||
|
if db_age > (35 * 86400):
|
||||||
|
os.remove(dbfile)
|
||||||
|
geoip_download()
|
||||||
|
except FileNotFoundError:
|
||||||
|
geoip_download()
|
||||||
|
|
||||||
|
reader = geoip2.database.Reader(dbfile)
|
||||||
|
|
||||||
|
return reader.city(ipaddress)
|
||||||
|
|
|
@ -20,7 +20,7 @@ class INIParser(object):
|
||||||
self.ombi_server = None
|
self.ombi_server = None
|
||||||
|
|
||||||
self.tautulli_enabled = False
|
self.tautulli_enabled = False
|
||||||
self.tautulli_server = None
|
self.tautulli_servers = []
|
||||||
|
|
||||||
self.asa_enabled = False
|
self.asa_enabled = False
|
||||||
self.asa = None
|
self.asa = None
|
||||||
|
@ -67,9 +67,10 @@ class INIParser(object):
|
||||||
future_days_run_seconds = self.config.getint(sonarr_section, 'future_days_run_seconds')
|
future_days_run_seconds = self.config.getint(sonarr_section, 'future_days_run_seconds')
|
||||||
queue_run_seconds = self.config.getint(sonarr_section, 'queue_run_seconds')
|
queue_run_seconds = self.config.getint(sonarr_section, 'queue_run_seconds')
|
||||||
|
|
||||||
self.sonarr_servers.append(SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days,
|
server = SonarrServer(server_id, scheme + url, apikey, verify_ssl, missing_days,
|
||||||
missing_days_run_seconds, future_days,
|
missing_days_run_seconds, future_days, future_days_run_seconds,
|
||||||
future_days_run_seconds, queue, queue_run_seconds))
|
queue, queue_run_seconds)
|
||||||
|
self.sonarr_servers.append(server)
|
||||||
|
|
||||||
# Parse Radarr options
|
# Parse Radarr options
|
||||||
try:
|
try:
|
||||||
|
@ -79,6 +80,8 @@ class INIParser(object):
|
||||||
self.radarr_enabled = True
|
self.radarr_enabled = True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.radarr_enabled = True
|
self.radarr_enabled = True
|
||||||
|
|
||||||
|
if self.sonarr_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:
|
||||||
|
@ -91,16 +94,32 @@ class INIParser(object):
|
||||||
self.radarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl))
|
self.radarr_servers.append(Server(server_id, scheme + url, apikey, verify_ssl))
|
||||||
|
|
||||||
# Parse Tautulli options
|
# Parse Tautulli options
|
||||||
if self.config.getboolean('global', 'tautulli'):
|
try:
|
||||||
|
if not self.config.getboolean('global', 'tautulli_server_ids'):
|
||||||
|
sys.exit('tautulli_server_ids must be either false, or a comma-separated list of server ids')
|
||||||
|
elif self.config.getint('global', 'tautulli_server_ids'):
|
||||||
|
self.tautulli_enabled = True
|
||||||
|
except ValueError:
|
||||||
self.tautulli_enabled = True
|
self.tautulli_enabled = True
|
||||||
url = self.config.get('tautulli', 'url')
|
|
||||||
fallback_ip = self.config.get('tautulli', 'fallback_ip')
|
|
||||||
apikey = self.config.get('tautulli', 'apikey')
|
|
||||||
scheme = 'https://' if self.config.getboolean('tautulli', 'ssl') else 'http://'
|
|
||||||
verify_ssl = self.config.getboolean('tautulli', 'verify_ssl')
|
|
||||||
db_name = self.config.get('tautulli', 'influx_db')
|
|
||||||
|
|
||||||
self.tautulli_server = TautulliServer(scheme + url, fallback_ip, apikey, verify_ssl, db_name)
|
if self.tautulli_enabled:
|
||||||
|
sids = self.config.get('global', 'tautulli_server_ids').strip(' ').split(',')
|
||||||
|
|
||||||
|
for server_id in sids:
|
||||||
|
tautulli_section = 'tautulli-' + server_id
|
||||||
|
url = self.config.get(tautulli_section, 'url')
|
||||||
|
fallback_ip = self.config.get(tautulli_section, 'fallback_ip')
|
||||||
|
apikey = self.config.get(tautulli_section, 'apikey')
|
||||||
|
scheme = 'https://' if self.config.getboolean(tautulli_section, 'ssl') else 'http://'
|
||||||
|
verify_ssl = self.config.getboolean(tautulli_section, 'verify_ssl')
|
||||||
|
get_activity = self.config.getboolean(tautulli_section, 'get_activity')
|
||||||
|
get_activity_run_seconds = self.config.getint(tautulli_section, 'get_activity_run_seconds')
|
||||||
|
get_sessions = self.config.getboolean(tautulli_section, 'get_sessions')
|
||||||
|
get_sessions_run_seconds = self.config.getint(tautulli_section, 'get_sessions_run_seconds')
|
||||||
|
|
||||||
|
server = TautulliServer(server_id, scheme + url, fallback_ip, apikey, verify_ssl, get_activity,
|
||||||
|
get_activity_run_seconds, get_sessions, get_sessions_run_seconds)
|
||||||
|
self.tautulli_servers.append(server)
|
||||||
|
|
||||||
# Parse Ombi Options
|
# Parse Ombi Options
|
||||||
if self.config.getboolean('global', 'ombi'):
|
if self.config.getboolean('global', 'ombi'):
|
||||||
|
|
|
@ -9,13 +9,13 @@ from Varken.helpers import TVShow, Queue
|
||||||
|
|
||||||
|
|
||||||
class SonarrAPI(object):
|
class SonarrAPI(object):
|
||||||
def __init__(self, sonarr_servers, influx_server):
|
def __init__(self, servers, influx_server):
|
||||||
# Set Time of initialization
|
# Set Time of initialization
|
||||||
self.now = datetime.now(timezone.utc).astimezone().isoformat()
|
self.now = datetime.now(timezone.utc).astimezone().isoformat()
|
||||||
self.today = str(date.today())
|
self.today = str(date.today())
|
||||||
self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username,
|
self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username,
|
||||||
influx_server.password, 'plex')
|
influx_server.password, 'plex')
|
||||||
self.servers = sonarr_servers
|
self.servers = servers
|
||||||
# Create session to reduce server web thread load, and globally define pageSize for all requests
|
# Create session to reduce server web thread load, and globally define pageSize for all requests
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.session.params = {'pageSize': 1000}
|
self.session.params = {'pageSize': 1000}
|
||||||
|
|
146
Varken/tautulli.py
Normal file
146
Varken/tautulli.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from geoip2.errors import AddressNotFoundError
|
||||||
|
from influxdb import InfluxDBClient
|
||||||
|
import requests
|
||||||
|
from Varken.helpers import TautulliStream, geo_lookup
|
||||||
|
from Varken.logger import logging
|
||||||
|
|
||||||
|
class TautulliAPI(object):
|
||||||
|
def __init__(self, servers, influx_server):
|
||||||
|
# Set Time of initialization
|
||||||
|
self.now = datetime.now(timezone.utc).astimezone().isoformat()
|
||||||
|
self.influx = InfluxDBClient(influx_server.url, influx_server.port, influx_server.username,
|
||||||
|
influx_server.password, 'plex')
|
||||||
|
self.servers = servers
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.endpoint = '/api/v2'
|
||||||
|
|
||||||
|
def influx_push(self, payload):
|
||||||
|
# TODO: error handling for failed connection
|
||||||
|
self.influx.write_points(payload)
|
||||||
|
|
||||||
|
@logging
|
||||||
|
def get_activity(self, notimplemented):
|
||||||
|
params = {'cmd': 'get_activity'}
|
||||||
|
influx_payload = []
|
||||||
|
|
||||||
|
for server in self.servers:
|
||||||
|
params['apikey'] = server.apikey
|
||||||
|
g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl)
|
||||||
|
get = g.json()['response']['data']
|
||||||
|
|
||||||
|
influx_payload.append(
|
||||||
|
{
|
||||||
|
"measurement": "Tautulli",
|
||||||
|
"tags": {
|
||||||
|
"type": "current_stream_stats",
|
||||||
|
"server": server.id
|
||||||
|
},
|
||||||
|
"time": self.now,
|
||||||
|
"fields": {
|
||||||
|
"stream_count": int(get['stream_count']),
|
||||||
|
"total_bandwidth": int(get['total_bandwidth']),
|
||||||
|
"wan_bandwidth": int(get['wan_bandwidth']),
|
||||||
|
"lan_bandwidth": int(get['lan_bandwidth']),
|
||||||
|
"transcode_streams": int(get['stream_count_transcode']),
|
||||||
|
"direct_play_streams": int(get['stream_count_direct_play']),
|
||||||
|
"direct_streams": int(get['stream_count_direct_stream'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.influx_push(influx_payload)
|
||||||
|
|
||||||
|
@logging
|
||||||
|
def get_sessions(self, notimplemented):
|
||||||
|
params = {'cmd': 'get_activity'}
|
||||||
|
influx_payload = []
|
||||||
|
|
||||||
|
for server in self.servers:
|
||||||
|
params['apikey'] = server.apikey
|
||||||
|
g = self.session.get(server.url + self.endpoint, params=params, verify=server.verify_ssl)
|
||||||
|
get = g.json()['response']['data']['sessions']
|
||||||
|
print(get)
|
||||||
|
sessions = [TautulliStream(**session) for session in get]
|
||||||
|
|
||||||
|
for session in sessions:
|
||||||
|
try:
|
||||||
|
geodata = geo_lookup(session.ip_address_public)
|
||||||
|
except (ValueError, AddressNotFoundError):
|
||||||
|
if server.fallback_ip:
|
||||||
|
geodata = geo_lookup(server.fallback_ip)
|
||||||
|
else:
|
||||||
|
my_ip = requests.get('http://ip.42.pl/raw').text
|
||||||
|
geodata = geo_lookup(my_ip)
|
||||||
|
|
||||||
|
if not all([geodata.location.latitude, geodata.location.longitude]):
|
||||||
|
latitude = 37.234332396
|
||||||
|
longitude = -115.80666344
|
||||||
|
else:
|
||||||
|
latitude = geodata.location.latitude
|
||||||
|
longitude = geodata.location.longitude
|
||||||
|
|
||||||
|
decision = session.transcode_decision
|
||||||
|
if decision == 'copy':
|
||||||
|
decision = 'direct stream'
|
||||||
|
|
||||||
|
video_decision = session.stream_video_decision
|
||||||
|
if video_decision == 'copy':
|
||||||
|
video_decision = 'direct stream'
|
||||||
|
elif video_decision == '':
|
||||||
|
video_decision = 'Music'
|
||||||
|
|
||||||
|
quality = session.stream_video_resolution
|
||||||
|
if not quality:
|
||||||
|
quality = session.container.upper()
|
||||||
|
elif quality in ('SD', 'sd', '4k'):
|
||||||
|
quality = session.stream_video_resolution.upper()
|
||||||
|
else:
|
||||||
|
quality = session.stream_video_resolution + 'p'
|
||||||
|
|
||||||
|
player_state = session.state.lower()
|
||||||
|
if player_state == 'playing':
|
||||||
|
player_state = 0
|
||||||
|
elif player_state == 'paused':
|
||||||
|
player_state = 1
|
||||||
|
elif player_state == 'buffering':
|
||||||
|
player_state = 3
|
||||||
|
|
||||||
|
influx_payload.append(
|
||||||
|
{
|
||||||
|
"measurement": "Tautulli",
|
||||||
|
"tags": {
|
||||||
|
"type": "Session",
|
||||||
|
"session_id": session.session_id,
|
||||||
|
"name": session.friendly_name,
|
||||||
|
"title": session.full_title,
|
||||||
|
"platform": session.platform,
|
||||||
|
"product_version": session.product_version,
|
||||||
|
"quality": quality,
|
||||||
|
"video_decision": video_decision.title(),
|
||||||
|
"transcode_decision": decision.title(),
|
||||||
|
"media_type": session.media_type.title(),
|
||||||
|
"audio_codec": session.audio_codec.upper(),
|
||||||
|
"audio_profile": session.audio_profile.upper(),
|
||||||
|
"stream_audio_codec": session.stream_audio_codec.upper(),
|
||||||
|
"quality_profile": session.quality_profile,
|
||||||
|
"progress_percent": session.progress_percent,
|
||||||
|
"region_code": geodata.subdivisions.most_specific.iso_code,
|
||||||
|
"location": geodata.city.name,
|
||||||
|
"full_location": '{} - {}'.format(geodata.subdivisions.most_specific.name,
|
||||||
|
geodata.city.name),
|
||||||
|
"latitude": latitude,
|
||||||
|
"longitude": longitude,
|
||||||
|
"player_state": player_state,
|
||||||
|
"device_type": session.platform,
|
||||||
|
"server": server.id
|
||||||
|
},
|
||||||
|
"time": self.now,
|
||||||
|
"fields": {
|
||||||
|
"session_id": session.session_id,
|
||||||
|
"session_key": session.session_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.influx_push(influx_payload)
|
|
@ -4,6 +4,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
|
||||||
|
|
||||||
|
|
||||||
def threaded(job, days=None):
|
def threaded(job, days=None):
|
||||||
|
@ -27,6 +28,15 @@ if __name__ == "__main__":
|
||||||
schedule.every(server.future_days_run_seconds).seconds.do(threaded, SONARR.get_future,
|
schedule.every(server.future_days_run_seconds).seconds.do(threaded, SONARR.get_future,
|
||||||
server.future_days)
|
server.future_days)
|
||||||
|
|
||||||
|
if CONFIG.tautulli_enabled:
|
||||||
|
TAUTULLI = TautulliAPI(CONFIG.tautulli_servers, CONFIG.influx_server)
|
||||||
|
|
||||||
|
for server in CONFIG.tautulli_servers:
|
||||||
|
if server.get_activity:
|
||||||
|
schedule.every(server.get_activity_run_seconds).seconds.do(threaded, TAUTULLI.get_activity)
|
||||||
|
if server.get_sessions:
|
||||||
|
schedule.every(server.get_sessions_run_seconds).seconds.do(threaded, TAUTULLI.get_sessions)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
schedule.run_pending()
|
schedule.run_pending()
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
|
@ -8,8 +8,8 @@
|
||||||
[global]
|
[global]
|
||||||
sonarr_server_ids = 1,2
|
sonarr_server_ids = 1,2
|
||||||
radarr_server_ids = 1,2
|
radarr_server_ids = 1,2
|
||||||
|
tautulli_server_ids = 1
|
||||||
ombi = true
|
ombi = true
|
||||||
tautulli = true
|
|
||||||
asa = false
|
asa = false
|
||||||
|
|
||||||
[influxdb]
|
[influxdb]
|
||||||
|
@ -18,6 +18,17 @@ port = 8086
|
||||||
username = root
|
username = root
|
||||||
password = root
|
password = root
|
||||||
|
|
||||||
|
[tautulli-1]
|
||||||
|
url = tautulli.domain.tld
|
||||||
|
fallback_ip = 0.0.0.0
|
||||||
|
apikey = xxxxxxxxxxxxxxxx
|
||||||
|
ssl = false
|
||||||
|
verify_ssl = true
|
||||||
|
get_activity = true
|
||||||
|
get_activity_run_seconds = 30
|
||||||
|
get_sessions = true
|
||||||
|
get_sessions_run_seconds = 30
|
||||||
|
|
||||||
[sonarr-1]
|
[sonarr-1]
|
||||||
url = sonarr1.domain.tld
|
url = sonarr1.domain.tld
|
||||||
apikey = xxxxxxxxxxxxxxxx
|
apikey = xxxxxxxxxxxxxxxx
|
||||||
|
@ -60,12 +71,6 @@ apikey = xxxxxxxxxxxxxxxx
|
||||||
ssl = false
|
ssl = false
|
||||||
verify_ssl = true
|
verify_ssl = true
|
||||||
|
|
||||||
[tautulli]
|
|
||||||
url = tautulli.domain.tld
|
|
||||||
fallback_ip = 0.0.0.0
|
|
||||||
apikey = xxxxxxxxxxxxxxxx
|
|
||||||
ssl = false
|
|
||||||
verify_ssl = true
|
|
||||||
|
|
||||||
[asa]
|
[asa]
|
||||||
url = firewall.domain.tld
|
url = firewall.domain.tld
|
||||||
|
|
Loading…
Reference in a new issue