Sonarr quality definition support

From the Quality Definitions (File Sizes) page of the TRaSH guides, you
can now sync the anime or non-anime quality profile to your Sonarr
instance. There is also a hybrid mode but that is not yet implemented.
pull/5/head
Robert Dailey 3 years ago
parent 7a5969874a
commit c5cc2035a8

@ -8,13 +8,16 @@ Automatically mirror TRaSH guides to your *darr instance.
Features list will continue to grow. See the limitations & roadmap section for more details!
* Preferred, Must Not Contain, and Must Contain lists from guides are reflected completely in
corresponding fields in release profiles in Sonarr.
* "Include Preferred when Renaming" is properly checked/unchecked depending on explicit mention of
this in the guides.
* Profiles get created if they do not exist, or updated if they already exist. Profiles get a unique
name based on the guide and this name is used to find them in subsequent runs.
* Tags can be added to any updated or created profiles.
* Sonarr Release Profiles
* Preferred, Must Not Contain, and Must Contain lists from guides are reflected completely in
corresponding fields in release profiles in Sonarr.
* "Include Preferred when Renaming" is properly checked/unchecked depending on explicit mention of
this in the guides.
* Profiles get created if they do not exist, or updated if they already exist. Profiles get a unique
name based on the guide and this name is used to find them in subsequent runs.
* Tags can be added to any updated or created profiles.
* Sonarr Quality Definitions
* Anime and Non-Anime quality definitions are now synced to Sonarr
## Requirements
@ -26,21 +29,61 @@ Features list will continue to grow. See the limitations & roadmap section for m
## Getting Started
I plan to add more tutorials/details/instructions later, but for now just run `trash.py --help`:
The only script you will need to be using is `src/trash.py`. If you've cloned my repository, simply
`cd` to the `src` directory so you can run `trash.py` directly:
```txt
usage: trash.py [-h] [--preview] [--debug] base_uri api_key
PS E:\code\TrashUpdater\src> .\trash.py -h
usage: trash.py [-h] {profile,quality} ...
Automatically mirror TRaSH guides to your *darr instance.
positional arguments:
base_uri The base URL for your Sonarr instance, for example `http://localhost:8989`.
api_key Your API key.
Automatically mirror TRaSH guides to your Sonarr/Radarr instance.
optional arguments:
-h, --help show this help message and exit
--preview Only display the processed markdown results and nothing else.
--debug Display additional logs useful for development/debug purposes
-h, --help show this help message and exit
subcommands:
Operations specific to different parts of the TRaSH guides
{profile,quality}
profile Pages of the guide that define profiles
quality Pages in the guide that provide quality definitions
```
The command line is structured into a series of subcommands that each handle a different area of the
guides. For example, you use a separate subcommand to sync quality definitions than you do release
profiles. Simply run `trash.py [subcommand] -h` to get help for `[subcommand]`, which can be any
supported subcommand listed in the top level help output.
### Examples
Some command line examples to show you how to use the script for various tasks. Note that most
command line options were generated on a Windows environment, so you will see OS-specific syntax
(e.g. backslashes). Obviously Python works on Linux systems too, so adjust the examples as needed
for your platform.
To preview what release profile information is parsed out of the Anime profile guide:
```txt
.\trash.py profile sonarr:anime --preview
```
To sync the anime release profiles to your Sonarr instance:
```txt
.\trash.py profile sonarr:anime --base-uri http://localhost:8989 --api-key a95cc792074644759fefe3ccab544f6e
```
To preview the Anime quality definition data parsed out of the Quality Definitions (file sizes) page
of the TRaSH guides:
```txt
.\trash.py quality sonarr:anime --preview
```
Sync the non-anime quality definition to Sonarr:
```txt
.\trash.py quality sonarr:non-anime --base-uri http://localhost:8989 --api-key a95cc792074644759fefe3ccab544f6e
```
## Important Notices

@ -0,0 +1,268 @@
[{
"quality": {
"id": 0,
"name": "Unknown",
"source": "unknown",
"resolution": 0
},
"title": "Unknown",
"weight": 1,
"minSize": 1.0,
"maxSize": 227.5,
"id": 1
},
{
"quality": {
"id": 1,
"name": "SDTV",
"source": "television",
"resolution": 480
},
"title": "SDTV",
"weight": 2,
"minSize": 2.0,
"maxSize": 100.0,
"id": 2
},
{
"quality": {
"id": 12,
"name": "WEBRip-480p",
"source": "webRip",
"resolution": 480
},
"title": "WEBRip-480p",
"weight": 3,
"minSize": 2.0,
"maxSize": 100.0,
"id": 3
},
{
"quality": {
"id": 8,
"name": "WEBDL-480p",
"source": "web",
"resolution": 480
},
"title": "WEBDL-480p",
"weight": 3,
"minSize": 2.0,
"maxSize": 100.0,
"id": 4
},
{
"quality": {
"id": 2,
"name": "DVD",
"source": "dvd",
"resolution": 480
},
"title": "DVD",
"weight": 4,
"minSize": 2.0,
"maxSize": 100.0,
"id": 5
},
{
"quality": {
"id": 13,
"name": "Bluray-480p",
"source": "bluray",
"resolution": 480
},
"title": "Bluray-480p",
"weight": 5,
"minSize": 2.0,
"maxSize": 100.0,
"id": 6
},
{
"quality": {
"id": 4,
"name": "HDTV-720p",
"source": "television",
"resolution": 720
},
"title": "HDTV-720p",
"weight": 6,
"minSize": 3.0,
"maxSize": 125.0,
"id": 7
},
{
"quality": {
"id": 9,
"name": "HDTV-1080p",
"source": "television",
"resolution": 1080
},
"title": "HDTV-1080p",
"weight": 7,
"minSize": 4.0,
"maxSize": 125.0,
"id": 8
},
{
"quality": {
"id": 10,
"name": "Raw-HD",
"source": "televisionRaw",
"resolution": 1080
},
"title": "Raw-HD",
"weight": 8,
"minSize": 4.0,
"id": 9
},
{
"quality": {
"id": 14,
"name": "WEBRip-720p",
"source": "webRip",
"resolution": 720
},
"title": "WEBRip-720p",
"weight": 9,
"minSize": 3.0,
"maxSize": 130.0,
"id": 10
},
{
"quality": {
"id": 5,
"name": "WEBDL-720p",
"source": "web",
"resolution": 720
},
"title": "WEBDL-720p",
"weight": 9,
"minSize": 3.0,
"maxSize": 130.0,
"id": 11
},
{
"quality": {
"id": 6,
"name": "Bluray-720p",
"source": "bluray",
"resolution": 720
},
"title": "Bluray-720p",
"weight": 10,
"minSize": 4.0,
"maxSize": 130.0,
"id": 12
},
{
"quality": {
"id": 15,
"name": "WEBRip-1080p",
"source": "webRip",
"resolution": 1080
},
"title": "WEBRip-1080p",
"weight": 11,
"minSize": 4.0,
"maxSize": 130.0,
"id": 13
},
{
"quality": {
"id": 3,
"name": "WEBDL-1080p",
"source": "web",
"resolution": 1080
},
"title": "WEBDL-1080p",
"weight": 11,
"minSize": 4.0,
"maxSize": 130.0,
"id": 14
},
{
"quality": {
"id": 7,
"name": "Bluray-1080p",
"source": "bluray",
"resolution": 1080
},
"title": "Bluray-1080p",
"weight": 12,
"minSize": 4.0,
"maxSize": 155.0,
"id": 15
},
{
"quality": {
"id": 20,
"name": "Bluray-1080p Remux",
"source": "blurayRaw",
"resolution": 1080
},
"title": "Bluray-1080p Remux",
"weight": 13,
"minSize": 35.0,
"id": 16
},
{
"quality": {
"id": 16,
"name": "HDTV-2160p",
"source": "television",
"resolution": 2160
},
"title": "HDTV-2160p",
"weight": 14,
"minSize": 35.0,
"maxSize": 199.9,
"id": 17
},
{
"quality": {
"id": 17,
"name": "WEBRip-2160p",
"source": "webRip",
"resolution": 2160
},
"title": "WEBRip-2160p",
"weight": 15,
"minSize": 59.0,
"id": 18
},
{
"quality": {
"id": 18,
"name": "WEBDL-2160p",
"source": "web",
"resolution": 2160
},
"title": "WEBDL-2160p",
"weight": 15,
"minSize": 59.0,
"id": 19
},
{
"quality": {
"id": 19,
"name": "Bluray-2160p",
"source": "bluray",
"resolution": 2160
},
"title": "Bluray-2160p",
"weight": 16,
"minSize": 59.0,
"id": 20
},
{
"quality": {
"id": 21,
"name": "Bluray-2160p Remux",
"source": "blurayRaw",
"resolution": 2160
},
"title": "Bluray-2160p Remux",
"weight": 17,
"minSize": 58.2,
"id": 21
}
]

@ -1,6 +1,7 @@
import requests
import json
from packaging import version # pip install packaging
from copy import deepcopy
from .server import Server
from ..profile_data import ProfileData
@ -13,6 +14,11 @@ class Sonarr(Server):
self.logger = logger
super().__init__(base_uri, key)
if not args.base_uri or not args.api_key:
raise ValueError('--base-uri and --api-key are required arguments when not using --preview')
self.do_version_check()
# --------------------------------------------------------------------------------------------------
@staticmethod
def get_error_message(response: requests.Response):
@ -94,3 +100,41 @@ class Sonarr(Server):
current_tags_json.append(r)
return current_tags_json
# --------------------------------------------------------------------------------------------------
def do_version_check(self):
# Since this script requires a specific version of v3 Sonarr that implements name support for
# release profiles, we perform that version check here and bail out if it does not meet a minimum
# required version.
minimum_version = version.parse('3.0.4.1098')
sonarr_version = self.get_version()
if sonarr_version < minimum_version:
raise RuntimeError(f'Your Sonarr version ({sonarr_version}) does not meet the minimum required version of {minimum_version} to use this script.')
exit(1)
# --------------------------------------------------------------------------------------------------
# GET /qualitydefinition
def get_quality_definition(self):
return self.request('get', '/qualitydefinition')
# --------------------------------------------------------------------------------------------------
# PUT /qualityDefinition/update
def update_quality_definition(self, sonarr_definition, guide_definition):
new_definition = deepcopy(sonarr_definition)
for quality, min, max in guide_definition:
entry = self.find_quality_definition_entry(new_definition, quality)
if not entry:
print(f'WARN: Quality definition lacks entry for {quality}; it will be skipped.')
continue
entry['minSize'] = min
entry['maxSize'] = max
self.request('put', '/qualityDefinition/update', new_definition)
# --------------------------------------------------------------------------------------------------
def find_quality_definition_entry(self, new_definition, quality):
for entry in new_definition:
if entry.get('quality').get('name') == quality:
return entry
return None

@ -1,15 +1,51 @@
import argparse
# class args: pass
class NoAction(argparse.Action):
def __init__(self, **kwargs):
kwargs.setdefault('default', argparse.SUPPRESS)
kwargs.setdefault('nargs', 0)
super(NoAction, self).__init__(**kwargs)
def setup_and_parse_args():
argparser = argparse.ArgumentParser(description='Automatically mirror TRaSH guides to your *darr instance.')
argparser.add_argument('--preview', help='Only display the processed markdown results and nothing else.',
def __call__(self, parser, namespace, values, option_string=None):
pass
def add_choices_argument(parser, variable_name, help_text, choices: dict):
parser.register('action', 'none', NoAction)
parser.add_argument(variable_name, help=help_text, metavar=variable_name.upper(), choices=choices.keys())
group = parser.add_argument_group(title=f'Choices for {variable_name.upper()}')
for choice,choice_help in choices.items():
group.add_argument(choice, help=choice_help, action='none')
def setup_and_parse_args(args_override=None):
parent_p = argparse.ArgumentParser(add_help=False)
parent_p.add_argument('--base-uri', help='The base URL for your Sonarr/Radarr instance, for example `http://localhost:8989`. Required if not doing --preview.')
parent_p.add_argument('--api-key', help='Your API key. Required if not doing --preview.')
parent_p.add_argument('--preview', help='Only display the processed markdown results and nothing else.',
action='store_true')
argparser.add_argument('--debug', help='Display additional logs useful for development/debug purposes',
parent_p.add_argument('--debug', help='Display additional logs useful for development/debug purposes',
action='store_true')
argparser.add_argument('--tags', help='Tags to assign to the profiles that are created or updated. These tags will replace any existing tags when updating profiles.',
parser = argparse.ArgumentParser(description='Automatically mirror TRaSH guides to your Sonarr/Radarr instance.')
subparsers = parser.add_subparsers(description='Operations specific to different parts of the TRaSH guides', dest='subcommand')
# Subcommands for 'profile'
profile_p = subparsers.add_parser('profile', help='Pages of the guide that define profiles',
parents=[parent_p])
add_choices_argument(profile_p, 'type', 'The specific guide type/page to pull data from.', {
'sonarr:anime': 'The anime release profile for Sonarr v3',
'sonarr:web-dl': 'The WEB-DL release profile for Sonarr v3'
})
profile_p.add_argument('--tags', help='Tags to assign to the profiles that are created or updated. These tags will replace any existing tags when updating profiles.',
nargs='+')
argparser.add_argument('base_uri', help='The base URL for your Sonarr instance, for example `http://localhost:8989`.')
argparser.add_argument('api_key', help='Your API key.')
return argparser.parse_args()#namespace=args)
# Subcommands for 'quality'
quality_p = subparsers.add_parser('quality', help='Pages in the guide that provide quality definitions',
parents=[parent_p])
add_choices_argument(quality_p, 'type', 'The specific guide type/page to pull data from.', {
'sonarr:anime': 'Choose the Sonarr quality definition best fit for anime',
'sonarr:non-anime': 'Choose the Sonarr quality definition best fit for tv shows (non-anime)',
'sonarr:hybrid': 'The script will generate a Sonarr quality definition that works best for all show types'
})
return parser.parse_args(args=args_override)

@ -0,0 +1,31 @@
import requests
import re
from collections import defaultdict
header_regex = re.compile(r'^#+')
table_row_regex = re.compile(r'\| *(.*?) *\| *([\d.]+) *\| *([\d.]+) *\|')
# --------------------------------------------------------------------------------------------------
def get_markdown():
trash_anime_markdown_url = 'https://raw.githubusercontent.com/TRaSH-/Guides/master/docs/Sonarr/V3/Sonarr-Quality-Settings-File-Size.md'
response = requests.get(trash_anime_markdown_url)
return response.content.decode('utf8')
# --------------------------------------------------------------------------------------------------
def parse_markdown(logger, markdown_content):
results = defaultdict(list)
table = None
for line in markdown_content.splitlines():
if not line:
continue
if header_regex.search(line):
category = 'sonarr:anime' if 'anime' in line.lower() else 'sonarr:non-anime'
table = results[category]
if len(table) > 0:
table = None
elif (match := table_row_regex.search(line)) and table is not None:
table.append((match.group(1), float(match.group(2)), float(match.group(3))))
return results

@ -42,3 +42,12 @@ def find_existing_profile(profile_name, existing_profiles):
if p.get('name') == profile_name:
return p
return None
# --------------------------------------------------------------------------------------------------
def quality_preview(name, definition):
print(name)
formats = ' {:<20} {:<10} {:<10}'
print(formats.format('Quality', 'Min', 'Max'))
for (quality, min, max) in definition:
print(formats.format(quality, min, max))
print('')

@ -0,0 +1,39 @@
## Sonarr Quality Definitions
| Quality | Minimum | Maximum |
| ------------------ | ------- | ------- |
| HDTV-720p | 17.9 | 67.5 |
| HDTV-1080p | 20 | 137.3 |
| WEBRip-720p | 20 | 137.3 |
| WEBDL-720p | 20 | 137.3 |
| Bluray-720p | 34.9 | 137.3 |
| WEBRip-1080p | 22 | 137.3 |
| WEBDL-1080p | 22 | 137.3 |
| Bluray-1080p | 50.4 | 227 |
| Bluray-1080p Remux | 69.1 | 400 |
| HDTV-2160p | 84.5 | 350 |
| WEBRip-2160p | 84.5 | 350 |
| WEBDL-2160p | 84.5 | 350 |
| Bluray-2160p | 94.6 | 400 |
| Bluray-2160p Remux | 204.4 | 400 |
------
### Sonarr Quality Definitions - Anime (Work in Progress)
| Quality | Minimum | Maximum |
| ------------------ | ------- | ------- |
| HDTV-720p | 2.3 | 51.4 |
| HDTV-1080p | 2.3 | 100 |
| WEBRip-720p | 4.3 | 100 |
| WEBDL-720p | 4.3 | 51.4 |
| Bluray-720p | 4.3 | 102.2 |
| WEBRip-1080p | 4.5 | 257.4 |
| WEBDL-1080p | 4.3 | 253.6 |
| Bluray-1080p | 4.3 | 258.1 |
| Bluray-1080p Remux | 0 | 400 |
| HDTV-2160p | 84.5 | 350 |
| WEBRip-2160p | 84.5 | 350 |
| WEBDL-2160p | 84.5 | 350 |
| Bluray-2160p | 94.6 | 400 |
| Bluray-2160p Remux | 204.4 | 400 |

@ -1,19 +1,15 @@
import trash.guide.anime as anime
import app.guide.anime as anime
from pathlib import Path
from tests.mock_logger import MockLogger
data_files = Path(__file__).parent / 'data'
class TestLogger:
def info(self, msg): pass
def debug(self, msg): pass
def test_parse_markdown_complete_doc():
md_file = data_files / 'test_parse_markdown_complete_doc.md'
with open(md_file) as file:
test_markdown = file.read()
results = anime.parse_markdown(TestLogger(), test_markdown)
results = anime.parse_markdown(MockLogger(), test_markdown)
assert len(results) == 1
profile = next(iter(results.values()))

@ -0,0 +1,55 @@
import app.guide.quality as quality
from pathlib import Path
from tests.mock_logger import MockLogger
data_files = Path(__file__).parent / 'data'
def test_parse_markdown_complete_doc():
md_file = data_files / 'test_parse_markdown_sonarr_quality_definitions.md'
with open(md_file) as file:
test_markdown = file.read()
results = quality.parse_markdown(MockLogger(), test_markdown)
# Dictionary: Key (header name (anime or non-anime)), list (quality definitions table rows)
assert len(results) == 2
table = results.get('sonarr:anime')
assert len(table) == 14
table_expected = [
('HDTV-720p', 2.3, 51.4),
('HDTV-1080p', 2.3, 100.0),
('WEBRip-720p', 4.3, 100.0),
('WEBDL-720p', 4.3, 51.4),
('Bluray-720p', 4.3, 102.2),
('WEBRip-1080p', 4.5, 257.4),
('WEBDL-1080p', 4.3, 253.6),
('Bluray-1080p', 4.3, 258.1),
('Bluray-1080p Remux', 0.0, 400.0),
('HDTV-2160p', 84.5, 350.0),
('WEBRip-2160p', 84.5, 350.0),
('WEBDL-2160p', 84.5, 350.0),
('Bluray-2160p', 94.6, 400.0),
('Bluray-2160p Remux', 204.4, 400.0)
]
assert sorted(table) == sorted(table_expected)
table = results.get('sonarr:non-anime')
assert len(table) == 14
table_expected = [
('HDTV-720p', 17.9, 67.5),
('HDTV-1080p', 20.0, 137.3),
('WEBRip-720p', 20.0, 137.3),
('WEBDL-720p', 20.0, 137.3),
('Bluray-720p', 34.9, 137.3),
('WEBRip-1080p', 22.0, 137.3),
('WEBDL-1080p', 22.0, 137.3),
('Bluray-1080p', 50.4, 227.0),
('Bluray-1080p Remux', 69.1, 400.0),
('HDTV-2160p', 84.5, 350.0),
('WEBRip-2160p', 84.5, 350.0),
('WEBDL-2160p', 84.5, 350.0),
('Bluray-2160p', 94.6, 400.0),
('Bluray-2160p Remux', 204.4, 400.0)
]
assert sorted(table) == sorted(table_expected)

@ -0,0 +1,3 @@
class MockLogger:
def info(self, msg): pass
def debug(self, msg): pass

@ -0,0 +1,19 @@
import pytest
import sys
from pathlib import Path
from app import cmd
from tests.mock_logger import MockLogger
sys.path.insert(0, Path(__name__).parent.parent)
import trash
class TestEntrypoint:
logger = MockLogger()
@staticmethod
def test_throw_without_required_arguments():
with pytest.raises(ValueError):
args = cmd.setup_and_parse_args(['profile', 'sonarr:anime', '--base-uri', 'value'])
trash.process_sonarr_profile(args, TestEntrypoint.logger)

@ -1,69 +1,94 @@
import requests
from packaging import version # pip install packaging
from app import guide
from app.api.sonarr import Sonarr
from app.guide import anime, utils
from app.guide import anime, utils, quality
from app.cmd import setup_and_parse_args
from app.logger import Logger
def process_sonarr_profile(args, logger):
profiles = anime.parse_markdown(logger, anime.get_trash_anime_markdown())
# A few false-positive profiles are added sometimes. We filter these out by checking if they
# actually have meaningful data attached to them, such as preferred terms. If they are mostly empty,
# we remove them here.
utils.filter_profiles(profiles)
if args.preview:
utils.print_terms_and_scores(profiles)
exit(0)
sonarr = Sonarr(args, logger)
# If tags were provided, ensure they exist. Tags that do not exist are added first, so that we
# may specify them with the release profile request payload.
tag_ids = []
if args.tags:
tags = sonarr.get_tags()
tags = sonarr.create_missing_tags(tags, args.tags[:])
logger.debug(f'Tags JSON: {tags}')
# Get a list of IDs that we can pass along with the request to update/create release
# profiles
tag_ids = [t['id'] for t in tags if t['label'] in args.tags]
logger.debug(f'Tag IDs: {tag_ids}')
# Obtain all of the existing release profiles first. If any were previously created by our script
# here, we favor replacing those instead of creating new ones, which would just be mostly duplicates
# (but with some differences, since there have likely been updates since the last run).
existing_profiles = sonarr.get_release_profiles()
for name, profile in profiles.items():
new_profile_name = f'[Trash] Anime - {name}'
profile_to_update = guide.utils.find_existing_profile(new_profile_name, existing_profiles)
if profile_to_update:
print(f'Updating existing profile: {new_profile_name}')
sonarr.update_existing_profile(profile_to_update, profile, tag_ids)
else:
print(f'Creating new profile: {new_profile_name}')
sonarr.create_release_profile(new_profile_name, profile, tag_ids)
def process_sonarr_quality(args, logger):
guide_definitions = quality.parse_markdown(logger, quality.get_markdown())
if args.type == 'sonarr:hybrid':
raise ValueError('Hybrid profile not implemented yet')
selected_definition = guide_definitions.get(args.type)
if args.preview:
utils.quality_preview(args.type, selected_definition)
exit(0)
print(f'Updating quality definition using {args.type}')
sonarr = Sonarr(args, logger)
definition = sonarr.get_quality_definition()
sonarr.update_quality_definition(definition, selected_definition)
def main():
args = setup_and_parse_args()
logger = Logger(args)
if args.subcommand == 'profile':
if args.type.startswith('sonarr:'):
process_sonarr_profile(args, logger)
elif args.type.startswith('radarr:'):
raise NotImplementedError('Radarr guide support is not implemented yet')
elif args.subcommand == 'quality':
if args.type.startswith('sonarr:'):
process_sonarr_quality(args, logger)
elif args.type.startswith('radarr:'):
raise NotImplementedError('Radarr quality support is not implemented yet')
if __name__ == '__main__':
try:
args = setup_and_parse_args()
logger = Logger(args)
sonarr = Sonarr(args, logger)
profiles = anime.parse_markdown(logger, anime.get_trash_anime_markdown())
# A few false-positive profiles are added sometimes. We filter these out by checking if they
# actually have meaningful data attached to them, such as preferred terms. If they are mostly empty,
# we remove them here.
utils.filter_profiles(profiles)
if args.preview:
utils.print_terms_and_scores(profiles)
exit(0)
# Since this script requires a specific version of v3 Sonarr that implements name support for
# release profiles, we perform that version check here and bail out if it does not meet a minimum
# required version.
minimum_version = version.parse('3.0.4.1098')
version = sonarr.get_version()
if version < minimum_version:
print(f'ERROR: Your Sonarr version ({version}) does not meet the minimum required version of {minimum_version} to use this script.')
exit(1)
# If tags were provided, ensure they exist. Tags that do not exist are added first, so that we
# may specify them with the release profile request payload.
tag_ids = []
if args.tags:
tags = sonarr.get_tags()
tags = sonarr.create_missing_tags(tags, args.tags[:])
logger.debug(f'Tags JSON: {tags}')
# Get a list of IDs that we can pass along with the request to update/create release
# profiles
tag_ids = [t['id'] for t in tags if t['label'] in args.tags]
logger.debug(f'Tag IDs: {tag_ids}')
# Obtain all of the existing release profiles first. If any were previously created by our script
# here, we favor replacing those instead of creating new ones, which would just be mostly duplicates
# (but with some differences, since there have likely been updates since the last run).
existing_profiles = sonarr.get_release_profiles()
for name, profile in profiles.items():
new_profile_name = f'[Trash] Anime - {name}'
profile_to_update = guide.utils.find_existing_profile(new_profile_name, existing_profiles)
if profile_to_update:
print(f'Updating existing profile: {new_profile_name}')
sonarr.update_existing_profile(profile_to_update, profile, tag_ids)
else:
print(f'Creating new profile: {new_profile_name}')
sonarr.create_release_profile(new_profile_name, profile, tag_ids)
main()
except requests.exceptions.HTTPError as e:
print(e)
if error_msg := Sonarr.get_error_message(e.response):
print(f'Response Message: {error_msg}')
exit(1)
except Exception as e:
print(f'ERROR: {e}')
exit(1)
Loading…
Cancel
Save