add regex filtering of quality profiles

pull/42/head
Junkbite 4 years ago
parent f8952424a8
commit e6d5a6ea97

@ -57,6 +57,7 @@ Syncs two Radarr/Sonarr/Lidarr servers through the web API. Useful for syncing a
url = http://127.0.0.1:8080
key = XXXXX
profile_filter = 1080p # add a filter to only sync contents belonging to this profile (can set by profile_filter_id as well)
quality_match = HD- # regex match to only sync content in instance A that matches this quality profile (example will match HD-720p and HD-1080p profiles but not HD4k)
[sonarrB]
url = http://127.0.0.1:8080
@ -159,7 +160,8 @@ To filter by profile in docker use `ARR_A_PROFILE_FILTER` or `ARR_A_PROFILE_FILT
* Set bidirectional sync with `SYNCARR_BIDIRECTIONAL_SYNC=1` (default 0)
* Set disable auto searching on new content with `SYNCARR_AUTO_SEARCH=0` (default 1)
* Set if you want to NOT monitor new content with `SYNCARR_MONITOR_NEW_CONTENT=0` (default 1)
* Match regex quality profiles with `*ARR_A_QUALITY_MATCH` or `*ARR_B_QUALITY_MATCH`
---
## Troubleshooting
If you need to troubleshoot syncarr, then you can either set the log level through the config file:

@ -9,7 +9,7 @@ import configparser
DEV = os.environ.get('DEV')
VER = '1.7.8'
VER = '1.8.0'
DEBUG_LINE = '-' * 20
V1_API_PATH = 'v1/'
@ -72,21 +72,25 @@ config.read(settingsFilename)
# get config settings from ENV or config files for Radarr
radarrA_url = get_config_value('RADARR_A_URL', 'url', 'radarrA')
radarrA_key = get_config_value('RADARR_A_KEY', 'key', 'radarrA')
radarrA_path = get_config_value('RADARR_A_PATH', 'path', 'radarrA')
radarrA_profile = get_config_value('RADARR_A_PROFILE', 'profile', 'radarrA')
radarrA_profile_id = get_config_value('RADARR_A_PROFILE_ID', 'profile_id', 'radarrA')
radarrA_profile_filter = get_config_value('RADARR_A_PROFILE_FILTER', 'profile_filter', 'radarrA')
radarrA_quality_match = get_config_value('SONARR_A_QUALITY_MATCH', 'quality_match', 'sonarrA')
radarrA_profile_filter_id = get_config_value('RADARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'radarrA')
radarrA_path = get_config_value('RADARR_A_PATH', 'path', 'radarrA')
radarrA_language = get_config_value('RADARR_A_LANGUAGE', 'language', 'radarrA')
radarrA_language_id = get_config_value('RADARR_A_LANGUAGE_ID', 'language_id', 'radarrA')
radarrA_quality_match = get_config_value('RADARR_A_QUALITY_MATCH', 'quality_match', 'radarrA')
radarrB_url = get_config_value('RADARR_B_URL', 'url', 'radarrB')
radarrB_key = get_config_value('RADARR_B_KEY', 'key', 'radarrB')
radarrB_path = get_config_value('RADARR_B_PATH', 'path', 'radarrB')
radarrB_profile = get_config_value('RADARR_B_PROFILE', 'profile', 'radarrB')
radarrB_profile_id = get_config_value('RADARR_B_PROFILE_ID', 'profile_id', 'radarrB')
radarrB_profile_filter = get_config_value('RADARR_B_PROFILE_FILTER', 'profile_filter', 'radarrB')
radarrB_profile_filter_id = get_config_value('RADARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'radarrB')
radarrB_language = get_config_value('RADARR_B_LANGUAGE', 'language', 'radarrB')
radarrB_language_id = get_config_value('RADARR_B_LANGUAGE_ID', 'language_id', 'radarrB')
radarrB_quality_match = get_config_value('RADARR_B_QUALITY_MATCH', 'quality_match', 'radarrB')
radarrB_path = get_config_value('RADARR_B_PATH', 'path', 'radarrB')
# get config settings from ENV or config files for Sonarr
sonarrA_url = get_config_value('SONARR_A_URL', 'url', 'sonarrA')
@ -96,10 +100,9 @@ sonarrA_profile = get_config_value('SONARR_A_PROFILE', 'profile', 'sonarrA')
sonarrA_profile_id = get_config_value('SONARR_A_PROFILE_ID', 'profile_id', 'sonarrA')
sonarrA_profile_filter = get_config_value('SONARR_A_PROFILE_FILTER', 'profile_filter', 'sonarrA')
sonarrA_profile_filter_id = get_config_value('SONARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'sonarrA')
sonarrA_quality_match = get_config_value('SONARR_A_QUALITY_MATCH', 'quality_match', 'sonarrA')
sonarrA_language = get_config_value('SONARR_A_LANGUAGE', 'language', 'sonarrA')
sonarrA_language_id = get_config_value('SONARR_A_LANGUAGE_ID', 'language_id', 'sonarrA')
sonarrA_quality_match = get_config_value('SONARR_A_QUALITY_MATCH', 'quality_match', 'sonarrA')
sonarrB_url = get_config_value('SONARR_B_URL', 'url', 'sonarrB')
sonarrB_key = get_config_value('SONARR_B_KEY', 'key', 'sonarrB')
@ -108,28 +111,32 @@ sonarrB_profile = get_config_value('SONARR_B_PROFILE', 'profile', 'sonarrB')
sonarrB_profile_id = get_config_value('SONARR_B_PROFILE_ID', 'profile_id', 'sonarrB')
sonarrB_profile_filter = get_config_value('SONARR_B_PROFILE_FILTER', 'profile_filter', 'sonarrB')
sonarrB_profile_filter_id = get_config_value('SONARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'sonarrB')
sonarrB_quality_match = get_config_value('SONARR_B_QUALITY_MATCH', 'quality_match', 'sonarrB')
sonarrB_language = get_config_value('SONARR_B_LANGUAGE', 'language', 'sonarrB')
sonarrB_language_id = get_config_value('SONARR_B_LANGUAGE_ID', 'language_id', 'sonarrB')
sonarrB_quality_match = get_config_value('SONARR_B_QUALITY_MATCH', 'quality_match', 'sonarrB')
# get config settings from ENV or config files for Lidarr
lidarrA_url = get_config_value('LIDARR_A_URL', 'url', 'lidarrA')
lidarrA_key = get_config_value('LIDARR_A_KEY', 'key', 'lidarrA')
lidarrA_path = get_config_value('LIDARR_A_PATH', 'path', 'lidarrA')
lidarrA_profile = get_config_value('LIDARR_A_PROFILE', 'profile', 'lidarrA')
lidarrA_profile_id = get_config_value('LIDARR_A_PROFILE_ID', 'profile_id', 'lidarrA')
lidarrA_profile_filter = get_config_value('LIDARR_A_PROFILE_FILTER', 'profile_filter', 'lidarrA')
lidarrA_profile_filter_id = get_config_value('LIDARR_A_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrA')
lidarrA_language = get_config_value('LIDARR_A_LANGUAGE', 'language', 'lidarrA')
lidarrA_language_id = get_config_value('LIDARR_A_LANGUAGE_ID', 'language_id', 'lidarrA')
lidarrA_quality_match = get_config_value('LIDARR_A_QUALITY_MATCH', 'quality_match', 'lidarrA')
lidarrA_path = get_config_value('LIDARR_A_PATH', 'path', 'lidarrA')
lidarrB_url = get_config_value('LIDARR_B_URL', 'url', 'lidarrB')
lidarrB_key = get_config_value('LIDARR_B_KEY', 'key', 'lidarrB')
lidarrB_path = get_config_value('LIDARR_B_PATH', 'path', 'lidarrB')
lidarrB_profile = get_config_value('LIDARR_B_PROFILE', 'profile', 'lidarrB')
lidarrB_profile_id = get_config_value('LIDARR_B_PROFILE_ID', 'profile_id', 'lidarrB')
lidarrB_profile_filter = get_config_value('LIDARR_B_PROFILE_FILTER', 'profile_filter', 'lidarrB')
lidarrB_profile_filter_id = get_config_value('LIDARR_B_PROFILE_FILTER_ID', 'profile_filter_id', 'lidarrB')
lidarrB_language = get_config_value('LIDARR_B_LANGUAGE', 'language', 'lidarrB')
lidarrB_language_id = get_config_value('LIDARR_B_LANGUAGE_ID', 'language_id', 'lidarrB')
lidarrB_quality_match = get_config_value('LIDARR_B_QUALITY_MATCH', 'quality_match', 'lidarrB')
lidarrB_path = get_config_value('LIDARR_B_PATH', 'path', 'lidarrB')
# set to search if config not set
@ -155,7 +162,10 @@ else:
# set to monitor if config not set
monitor_new_content = get_config_value('SYNCARR_MONITOR_NEW_CONTENT', 'monitor_new_content', 'general')
if monitor_new_content is not None:
monitor_new_content = int(monitor_new_content)
try:
monitor_new_content = int(monitor_new_content)
except ValueError:
monitor_new_content = 1
else:
monitor_new_content = 1
@ -214,8 +224,9 @@ instanceA_path = ''
instanceA_profile = ''
instanceA_profile_id = ''
instanceA_profile_filter = ''
instanceA_language_id = ''
instanceA_profile_filter_id = ''
instanceA_language = ''
instanceA_language_id = ''
instanceA_quality_match = ''
instanceB_url = ''
@ -224,8 +235,9 @@ instanceB_path = ''
instanceB_profile = ''
instanceB_profile_id = ''
instanceB_profile_filter = ''
instanceB_language_id = ''
instanceB_profile_filter_id = ''
instanceB_language = ''
instanceB_language_id = ''
instanceB_quality_match = ''
@ -247,19 +259,25 @@ content_id_key = '' # the unique id for a content item
if radarrA_url and radarrB_url:
instanceA_url = radarrA_url
instanceA_key = radarrA_key
instanceA_path = radarrA_path
instanceA_profile = radarrA_profile
instanceA_profile_id = radarrA_profile_id
instanceA_profile_filter = radarrA_profile_filter
instanceA_profile_filter_id = radarrA_profile_filter_id
instanceA_path = radarrA_path
instanceA_language = radarrA_language
instanceA_language_id = radarrA_language_id
instanceA_quality_match = radarrA_quality_match
instanceB_url = radarrB_url
instanceB_key = radarrB_key
instanceB_path = radarrB_path
instanceB_profile = radarrB_profile
instanceB_profile_id = radarrB_profile_id
instanceB_profile_filter = radarrB_profile_filter
instanceB_profile_filter_id = radarrB_profile_filter_id
instanceB_path = radarrB_path
instanceB_language = radarrB_language
instanceB_language_id = radarrB_language_id
instanceB_quality_match = radarrB_quality_match
api_version = V2_API_PATH # radarr v2 doesnt have version in api url
api_content_path = 'movie'
@ -272,19 +290,25 @@ if radarrA_url and radarrB_url:
elif lidarrA_url and lidarrB_url:
instanceA_url = lidarrA_url
instanceA_key = lidarrA_key
instanceA_path = lidarrA_path
instanceA_profile = lidarrA_profile
instanceA_profile_id = lidarrA_profile_id
instanceA_profile_filter = lidarrA_profile_filter
instanceA_profile_filter_id = lidarrA_profile_filter_id
instanceA_path = lidarrA_path
instanceA_language = lidarrA_language
instanceA_language_id = lidarrA_language_id
instanceA_quality_match = lidarrA_quality_match
instanceB_url = lidarrB_url
instanceB_key = lidarrB_key
instanceB_path = lidarrB_path
instanceB_profile = lidarrB_profile
instanceB_profile_id = lidarrB_profile_id
instanceB_profile_filter = lidarrB_profile_filter
instanceB_profile_filter_id = lidarrB_profile_filter_id
instanceB_path = lidarrB_path
instanceB_language = lidarrB_language
instanceB_language_id = lidarrB_language_id
instanceB_quality_match = lidarrB_quality_match
api_version = V1_API_PATH
api_content_path = 'artist'
@ -304,6 +328,7 @@ elif sonarrA_url and sonarrB_url:
instanceA_profile_filter_id = sonarrA_profile_filter_id
instanceA_language = sonarrA_language
instanceA_language_id = sonarrA_language_id
instanceA_quality_match = sonarrA_quality_match
instanceB_url = sonarrB_url
instanceB_key = sonarrB_key
@ -314,6 +339,7 @@ elif sonarrA_url and sonarrB_url:
instanceB_profile_filter_id = sonarrB_profile_filter_id
instanceB_language = sonarrB_language
instanceB_language_id = sonarrB_language_id
instanceB_quality_match = sonarrB_quality_match
api_version = V3_API_PATH # for sonarr try v3 first
api_content_path = 'series'
@ -384,14 +410,32 @@ logger.debug({
'instanceA_url': instanceA_url,
'instanceA_key': instanceA_key,
'instanceA_path': instanceA_path,
'instanceA_profile': instanceA_profile,
'instanceA_profile_id': instanceA_profile_id,
'instanceA_profile_filter': instanceA_profile_filter,
'instanceA_profile_filter_id': instanceA_profile_filter_id,
'instanceA_language': instanceA_language,
'instanceA_language_id': instanceA_language_id,
'instanceA_quality_match': instanceA_quality_match,
'instanceB_url': instanceB_url,
'instanceB_key': instanceB_key,
'instanceB_path': instanceB_path,
'instanceB_profile': instanceB_profile,
'instanceB_profile_id': instanceB_profile_id,
'instanceB_profile_filter': instanceB_profile_filter,
'instanceB_profile_filter_id': instanceB_profile_filter_id,
'instanceB_language': instanceB_language,
'instanceB_language_id': instanceB_language_id,
'instanceB_quality_match': instanceB_quality_match,
'api_content_path': api_content_path,
'api_profile_path': api_profile_path,
'api_language_path': api_language_path,
'is_sonarr': is_sonarr,
'is_lidarr': is_lidarr,
'monitor_new_content': monitor_new_content,
'sync_bidirectionally': sync_bidirectionally,
'auto_search': auto_search,
@ -429,7 +473,6 @@ if sync_bidirectionally:
logger.error('profile_id or profile is required for *arr instance A if sync bidirectionally is enabled')
sys.exit(0)
if not instanceB_profile_id and not instanceB_profile:
logger.error('profile_id or profile is required for *arr instance B')
sys.exit(0)

@ -7,15 +7,16 @@ import json
import configparser
import sys
import time
import re
from config import (
instanceA_url, instanceA_key, instanceA_path, instanceA_profile,
instanceA_profile_id, instanceA_profile_filter, instanceA_profile_filter_id,
instanceA_language_id, instanceA_language,
instanceA_language_id, instanceA_language, instanceA_quality_match,
instanceB_url, instanceB_key, instanceB_path, instanceB_profile,
instanceB_profile_id, instanceB_profile_filter, instanceB_profile_filter_id,
instanceB_language_id, instanceB_language,
instanceB_language_id, instanceB_language, instanceB_quality_match,
content_id_key, logger, is_sonarr, is_radarr, is_lidarr,
get_status_path, get_content_path, get_profile_path, get_language_path,
@ -90,32 +91,31 @@ def get_new_content_payload(content, instance_path, instance_profile_id, instanc
logger.debug(payload)
return payload
def get_profile_from_id(instance_session, instance_url, instance_key, instance_profile, instance_name=''):
def get_quality_profiles(instance_session, instance_url, instance_key):
instance_profile_url = get_profile_path(instance_url, instance_key)
profiles_response = instance_session.get(instance_profile_url)
if profiles_response.status_code != 200:
logger.error(
f'Could not get profile id from {instance_profile_url}')
logger.error(f'Could not get profile id from {instance_profile_url}')
sys.exit(0)
instance_profiles = None
try:
instance_profiles = profiles_response.json()
return instance_profiles
except:
logger.error(
f'Could not decode profile id from {instance_profile_url}')
logger.error(f'Could not decode profile id from {instance_profile_url}')
sys.exit(0)
def get_profile_from_id(instance_session, instance_url, instance_key, instance_profile, instance_name=''):
instance_profiles = get_quality_profiles(instance_session=instance_session, instance_url=instance_url, instance_key=instance_key)
profile = next((item for item in instance_profiles if item["name"].lower() == instance_profile.lower()), False)
if not profile:
logger.error('Could not find profile_id for instance {} profile {}'.format(
instance_name, instance_profile))
logger.error('Could not find profile_id for instance {} profile {}'.format(instance_name, instance_profile))
sys.exit(0)
instance_profile_id = profile.get('id')
logger.debug(
f'found profile_id (instance{instance_name}) "{instance_profile_id}" from profile "{instance_profile}"')
logger.debug(f'found profile_id (instance{instance_name}) "{instance_profile_id}" from profile "{instance_profile}"')
return instance_profile_id
@ -156,13 +156,24 @@ def get_language_from_id(instance_session, instance_url, instance_key, instance_
def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds,
instanceB_path, instanceB_profile_id, instanceB_session,
instanceB_url, profile_filter_id, instanceB_key):
instanceB_path, instanceB_profile_id, instanceA_profile_filter_id,
instanceA_session, instanceA_url, instanceA_key,
instanceB_session, instanceB_url, instanceB_key, instanceA_quality_match):
search_ids = []
# if given instance A profile id then we want to filter out content without that id
if profile_filter_id:
logging.info(f'only filtering content with profile_filter_id {profile_filter_id}')
if instanceA_profile_filter_id:
logging.info(f'only filtering content with instanceA_profile_filter_id {instanceA_profile_filter_id}')
# if we watn to filter by quality profile we need to get all profiles first
instance_profiles = None
if instanceA_quality_match:
instance_profiles = get_quality_profiles(instance_session=instanceA_session, instance_url=instanceA_url, instance_key=instanceA_key)
quality_profile_matches = [item for item in instance_profiles if re.match(instanceA_quality_match, item["name"])]
quality_profile_match_ids = [item["id"] for item in quality_profile_matches]
_quality_profile_matches = [f'{str(item["id"])} ({item["name"]})' for item in quality_profile_matches]
_debug_matches = ', '.join(_quality_profile_matches)
logging.debug(f'Matching regex quality profiles {_debug_matches}')
# for each content id in instance A, check if it needs to be synced to instance B
for content in instanceA_contents:
@ -170,12 +181,19 @@ def sync_servers(instanceA_contents, instanceB_language_id, instanceB_contentIds
title = content.get('title') or content.get('artistName')
# if given this, we want to filter from instance by profile id
if profile_filter_id:
content_profile_id = content.get('qualityProfileId')
if profile_filter_id != content_profile_id:
logging.debug(f'Skipping content {title} - mismatched profile_id {content_profile_id} with filter_id {profile_filter_id}')
if instanceA_profile_filter_id:
quality_profile_id = content.get('qualityProfileId')
if instanceA_profile_filter_id != quality_profile_id:
logging.debug(f'Skipping content {title} - mismatched quality_profile_id {quality_profile_id} with instanceA_profile_filter_id {instanceA_profile_filter_id}')
continue
# if given quality filter we want to filter if quality from instanceA isnt high enough yet
if instanceA_quality_match and instance_profiles:
quality_profile_id = content.get('qualityProfileId')
if quality_profile_id not in quality_profile_match_ids:
logging.debug(f'Skipping content {title} - mismatched quality_profile_id {quality_profile_id} with instanceA_quality_match {instanceA_quality_match}')
continue
logging.info(f'syncing content title "{title}"')
# get the POST payload and sync content to instance B
@ -272,7 +290,7 @@ def check_status(instance_session, instance_url, instance_key, instance_name='',
def sync_content():
global instanceA_profile_id, instanceA_profile, instanceB_profile_id, instanceB_profile, instanceA_profile_filter, instanceA_profile_filter_id, instanceB_profile_filter, instanceB_profile_filter_id, tested_api_version, instanceA_language_id, instanceA_language, instanceB_language_id, instanceB_language
global instanceA_profile_id, instanceA_profile, instanceB_profile_id, instanceB_profile, instanceA_profile_filter, instanceA_profile_filter_id, instanceB_profile_filter, instanceB_profile_filter_id, tested_api_version, instanceA_language_id, instanceA_language, instanceB_language_id, instanceB_language, instanceA_quality_match, instanceB_quality_match
# get sessions
instanceA_session = requests.Session()
@ -356,8 +374,12 @@ def sync_content():
instanceB_profile_id=instanceB_profile_id,
instanceB_session=instanceB_session,
instanceB_url=instanceB_url,
profile_filter_id=instanceA_profile_filter_id,
instanceA_profile_filter_id=instanceA_profile_filter_id,
instanceB_key=instanceB_key,
instanceA_quality_match=instanceA_quality_match,
instanceA_session=instanceA_session,
instanceA_url=instanceA_url,
instanceA_key=instanceA_key,
)
# if given bidirectional flag then sync from instance B to instance A
@ -372,8 +394,12 @@ def sync_content():
instanceB_profile_id=instanceA_profile_id,
instanceB_session=instanceA_session,
instanceB_url=instanceA_url,
profile_filter_id=instanceB_profile_filter_id,
instanceA_profile_filter_id=instanceB_profile_filter_id,
instanceB_key=instanceA_key,
instanceA_quality_match=instanceB_quality_match,
instanceA_session=instanceB_session,
instanceA_url=instanceB_url,
instanceA_key=instanceB_key,
)
########################################################################################################################

Loading…
Cancel
Save