mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-29 22:10:52 +01:00
v0.1.3
- improved debugging info for exports - improved user control while exporting - added config file to add paths / api keys - added master setup for radarr / sonarr to sync from (not implemented yet) - adjusted custom format file names to be consistent with qp's - combined import functionality into a single script - improved debugging info for imports (functionality is still the same) - error messages for profile naming conflicts, bad auth and missing app - up to date set of profiles / custom formats as of 19/01/24
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# Profilarr
|
||||
|
||||
Profilarr is a Python-based tool that enables seamless synchronization of custom formats and quality profiles in Radarr / Sonarr. It's designed to aid users in sharing / importing custom formats & quality profiles seamlessly.
|
||||
Profilarr is a Python-based tool that enables synchronization of custom formats and quality profiles in Radarr / Sonarr. It's designed to aid users in exporting / importing custom formats & quality profiles seamlessly.
|
||||
|
||||
Companion tool to Dictionarry to mass import custom formats / profiles quickly.
|
||||
|
||||
## ⚠️ Before Continuing
|
||||
|
||||
- **This tool will overwrite any custom formats in your Radarr installation that have the same name.**
|
||||
- **This tool will overwrite any custom formats in your \*arr installation that have the same name.**
|
||||
- **Custom Formats MUST be imported before syncing any premade profile.**
|
||||
|
||||
## 🛠️ Installation
|
||||
@@ -20,7 +20,7 @@ Companion tool to Dictionarry to mass import custom formats / profiles quickly.
|
||||
|
||||
1. Download the Profilarr zip file from the release section.
|
||||
2. Extract its contents into a folder.
|
||||
3. Open `import.py` in a text editor of your choice.
|
||||
3. Open either of the `import.py` files in a text editor of your choice.
|
||||
- Add your Radarr / Sonarr API key to the designated section.
|
||||
- Modify the Base URL if needed
|
||||
4. Save the changes and close the text editor.
|
||||
|
||||
25
config.json
Normal file
25
config.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"master": {
|
||||
"sonarr": {
|
||||
"base_url": "http://localhost:8989",
|
||||
"api_key": "API_GOES_HERE"
|
||||
},
|
||||
"radarr": {
|
||||
"base_url": "http://localhost:7878",
|
||||
"api_key": "API_GOES_HERE"
|
||||
}
|
||||
},
|
||||
"extra_installations": [
|
||||
{
|
||||
"name": "example",
|
||||
"sonarr": {
|
||||
"base_url": "http://otherhost1:8989",
|
||||
"api_key": "API_GOES_HERE"
|
||||
},
|
||||
"radarr": {
|
||||
"base_url": "http://otherhost1:7878",
|
||||
"api_key": "API_GOES_HERE"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
216
export.py
216
export.py
@@ -3,93 +3,155 @@ import requests
|
||||
import os
|
||||
import re
|
||||
|
||||
# Define constants
|
||||
base_url = "http://localhost:7878"
|
||||
api_key = "API_GOES_HERE"
|
||||
# ANSI escape sequences for colors
|
||||
class Colors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
|
||||
# Define parameters and headers
|
||||
params = {"apikey": api_key}
|
||||
headers = {"X-Api-Key": api_key}
|
||||
files = {'file': ('', '')} # Empty file to force multipart/form-data
|
||||
# Load configuration for main app
|
||||
with open('config.json', 'r') as config_file:
|
||||
config = json.load(config_file)['master']
|
||||
|
||||
# Login
|
||||
login_url = f"{base_url}/login"
|
||||
response = requests.get(login_url, params=params, headers=headers, files=files)
|
||||
def get_user_choice():
|
||||
choice = input("Enter an app to export from (radarr/sonarr): ").lower()
|
||||
while choice not in ["radarr", "sonarr"]:
|
||||
print(Colors.FAIL + "Invalid input. Please enter either 'radarr' or 'sonarr'." + Colors.ENDC)
|
||||
choice = input("Enter the source (radarr/sonarr): ").lower()
|
||||
print()
|
||||
return choice
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Login Failed! (HTTP {response.status_code})")
|
||||
print("Response Content: ", response.content)
|
||||
exit()
|
||||
def get_export_choice():
|
||||
print(Colors.HEADER + "Choose what to export:" + Colors.ENDC)
|
||||
print("1. Custom Formats")
|
||||
print("2. Quality Profiles")
|
||||
print("3. Both")
|
||||
choice = input("Enter your choice (1/2/3): ").strip()
|
||||
while choice not in ["1", "2", "3"]:
|
||||
print(Colors.FAIL + "Invalid input. Please enter 1, 2, or 3." + Colors.ENDC)
|
||||
choice = input("Enter your choice (1/2/3): ").strip()
|
||||
print()
|
||||
return choice
|
||||
|
||||
def export_cf():
|
||||
# Prompt the user to specify the source (Radarr or Sonarr)
|
||||
source = input("Enter the source (radarr/sonarr): ").lower()
|
||||
while source not in ["radarr", "sonarr"]:
|
||||
print("Invalid input. Please enter either 'radarr' or 'sonarr'.")
|
||||
source = input("Enter the source (radarr/sonarr): ").lower()
|
||||
|
||||
custom_format_url = f"{base_url}/api/v3/customformat"
|
||||
response = requests.get(custom_format_url, params=params, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
|
||||
# Remove 'id' from each custom format
|
||||
for custom_format in data:
|
||||
custom_format.pop('id', None)
|
||||
|
||||
# Save to JSON file with adjusted name based on source
|
||||
file_path = f'./custom_formats/{source}_custom_formats.json'
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
print(f"Custom Formats have been saved to '{file_path}'")
|
||||
else:
|
||||
print(f"Failed to retrieve custom formats! (HTTP {response.status_code})")
|
||||
print("Response Content: ", response.content)
|
||||
def get_app_config(source):
|
||||
app_config = config[source]
|
||||
return app_config['base_url'], app_config['api_key']
|
||||
|
||||
def sanitize_filename(filename):
|
||||
# Replace any characters not allowed in filenames with _
|
||||
sanitized_filename = re.sub(r'[\\/*?:"<>|]', '_', filename)
|
||||
return sanitized_filename
|
||||
|
||||
def export_qf():
|
||||
# Prompt the user to specify the source (Radarr or Sonarr)
|
||||
source = input("Enter the source (radarr/sonarr): ").lower()
|
||||
while source not in ["radarr", "sonarr"]:
|
||||
print("Invalid input. Please enter either 'radarr' or 'sonarr'.")
|
||||
source = input("Enter the source (radarr/sonarr): ").lower()
|
||||
|
||||
# Capitalize the first letter of the source
|
||||
source = source.capitalize()
|
||||
|
||||
response = requests.get(f"{base_url}/api/v3/qualityprofile", params=params, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
quality_profiles = response.json()
|
||||
|
||||
# Ensure the ./profiles directory exists
|
||||
if not os.path.exists('./profiles'):
|
||||
os.makedirs('./profiles')
|
||||
|
||||
# Process each profile separately
|
||||
for profile in quality_profiles:
|
||||
profile.pop('id', None) # Remove the 'id' field
|
||||
|
||||
# Use the name of the profile to create a filename and append the source
|
||||
profile_name = profile.get('name', 'unnamed_profile') # Use a default name if the profile has no name
|
||||
profile_name = sanitize_filename(profile_name) # Sanitize the filename
|
||||
profile_filename = f"{profile_name} ({source}).json"
|
||||
profile_filepath = os.path.join('./profiles', profile_filename)
|
||||
|
||||
# Save the individual profile to a file as a single-element array
|
||||
with open(profile_filepath, 'w') as file:
|
||||
json.dump([profile], file, indent=4) # Note the [profile], it will make it an array with a single element
|
||||
|
||||
print("Quality profiles have been successfully saved to the ./profiles directory")
|
||||
def handle_response_errors(response):
|
||||
if response.status_code == 401:
|
||||
print(Colors.FAIL + "Authentication error: Invalid API key." + Colors.ENDC)
|
||||
elif response.status_code == 403:
|
||||
print(Colors.FAIL + "Forbidden: Access is denied." + Colors.ENDC)
|
||||
else:
|
||||
print("Failed to retrieve quality profiles!")
|
||||
print(Colors.FAIL + f"An error occurred! (HTTP {response.status_code})" + Colors.ENDC)
|
||||
print("Response Content: ", response.content.decode('utf-8'))
|
||||
|
||||
def print_saved_items(items, item_type):
|
||||
if len(items) > 10:
|
||||
items_to_display = items[:10]
|
||||
for item in items_to_display:
|
||||
print(f" - {item}")
|
||||
print(f"... and {len(items) - 10} more.")
|
||||
else:
|
||||
for item in items:
|
||||
print(f" - {item}")
|
||||
|
||||
def ensure_directory_exists(directory):
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
print(Colors.OKBLUE + f"Created directory: {directory}" + Colors.ENDC)
|
||||
|
||||
def export_cf(source):
|
||||
ensure_directory_exists('./custom_formats') # Ensure the directory exists
|
||||
|
||||
base_url, api_key = get_app_config(source)
|
||||
headers = {"X-Api-Key": api_key}
|
||||
params = {"apikey": api_key}
|
||||
|
||||
print(Colors.OKBLUE + f"Attempting to access {source.capitalize()} at {base_url}" + Colors.ENDC)
|
||||
|
||||
custom_format_url = f"{base_url}/api/v3/customformat"
|
||||
|
||||
try:
|
||||
response = requests.get(custom_format_url, params=params, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(Colors.OKGREEN + f"Found {len(data)} custom formats." + Colors.ENDC)
|
||||
|
||||
saved_formats = []
|
||||
for custom_format in data:
|
||||
custom_format.pop('id', None)
|
||||
saved_formats.append(custom_format['name'])
|
||||
|
||||
file_path = f'./custom_formats/Custom Formats ({source.capitalize()}).json'
|
||||
with open(file_path, 'w') as f:
|
||||
json.dump(data, f, indent=4)
|
||||
print_saved_items(saved_formats, "Custom Formats")
|
||||
print(Colors.OKGREEN + f"Saved to '{file_path}'" + Colors.ENDC)
|
||||
print()
|
||||
else:
|
||||
handle_response_errors(response)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(Colors.FAIL + f"Failed to connect to {source.capitalize()}! Please check if it's running and accessible." + Colors.ENDC)
|
||||
|
||||
|
||||
|
||||
def export_qf(source):
|
||||
ensure_directory_exists('./profiles') # Ensure the directory exists
|
||||
|
||||
base_url, api_key = get_app_config(source)
|
||||
headers = {"X-Api-Key": api_key}
|
||||
params = {"apikey": api_key}
|
||||
|
||||
print(Colors.OKBLUE + f"Attempting to access {source.capitalize()} at {base_url}" + Colors.ENDC)
|
||||
|
||||
try:
|
||||
response = requests.get(f"{base_url}/api/v3/qualityprofile", params=params, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
quality_profiles = response.json()
|
||||
print(Colors.OKGREEN + f"Found {len(quality_profiles)} quality profiles." + Colors.ENDC)
|
||||
|
||||
if not os.path.exists('./profiles'):
|
||||
os.makedirs('./profiles')
|
||||
|
||||
saved_profiles = []
|
||||
for profile in quality_profiles:
|
||||
profile.pop('id', None)
|
||||
profile_name = profile.get('name', 'unnamed_profile')
|
||||
profile_name = sanitize_filename(profile_name)
|
||||
profile_filename = f"{profile_name} ({source.capitalize()}).json"
|
||||
profile_filepath = os.path.join('./profiles', profile_filename)
|
||||
saved_profiles.append(profile_name)
|
||||
|
||||
with open(profile_filepath, 'w') as file:
|
||||
json.dump([profile], file, indent=4)
|
||||
print_saved_items(saved_profiles, "Quality Profiles")
|
||||
print(Colors.OKGREEN + "Saved to the ./profiles directory" + Colors.ENDC)
|
||||
print()
|
||||
else:
|
||||
handle_response_errors(response)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(Colors.FAIL + f"Failed to connect to {source.capitalize()}! Please check if it's running and accessible." + Colors.ENDC)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
export_cf()
|
||||
export_qf()
|
||||
user_choice = get_user_choice()
|
||||
export_choice = get_export_choice()
|
||||
|
||||
if export_choice in ["1", "3"]:
|
||||
export_cf(user_choice)
|
||||
if export_choice in ["2", "3"]:
|
||||
export_qf(user_choice)
|
||||
|
||||
206
import.py
Normal file
206
import.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import json
|
||||
import requests
|
||||
import os
|
||||
|
||||
# ANSI escape sequences for colors
|
||||
class Colors:
|
||||
HEADER = '\033[95m'
|
||||
OKBLUE = '\033[94m'
|
||||
OKGREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
FAIL = '\033[91m'
|
||||
ENDC = '\033[0m'
|
||||
BOLD = '\033[1m'
|
||||
UNDERLINE = '\033[4m'
|
||||
|
||||
# Load configuration for main app
|
||||
with open('config.json', 'r') as config_file:
|
||||
config = json.load(config_file)
|
||||
|
||||
def print_success(message):
|
||||
print(Colors.OKGREEN + message + Colors.ENDC)
|
||||
|
||||
def print_error(message):
|
||||
print(Colors.FAIL + message + Colors.ENDC)
|
||||
|
||||
def print_connection_error():
|
||||
print(Colors.FAIL + "Failed to connect to the service! Please check if it's running and accessible." + Colors.ENDC)
|
||||
|
||||
def get_user_choice():
|
||||
choice = input("Enter the app you want to import to (radarr/sonarr): ").lower()
|
||||
while choice not in ["radarr", "sonarr"]:
|
||||
print_error("Invalid input. Please enter either 'radarr' or 'sonarr'.")
|
||||
choice = input("Enter the source (radarr/sonarr): ").lower()
|
||||
return choice
|
||||
|
||||
def get_import_choice():
|
||||
print()
|
||||
print(Colors.HEADER + "Choose what to import:" + Colors.ENDC)
|
||||
print("1. Custom Formats")
|
||||
print("2. Quality Profiles")
|
||||
choice = input("Enter your choice (1/2): ").strip()
|
||||
while choice not in ["1", "2"]:
|
||||
print_error("Invalid input. Please enter 1 or 2.")
|
||||
choice = input("Enter your choice (1/2): ").strip()
|
||||
return choice
|
||||
|
||||
def get_app_config(source):
|
||||
return config['master'][source]
|
||||
|
||||
def select_file(directory):
|
||||
files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
|
||||
print()
|
||||
print(Colors.OKBLUE + "Available files:" + Colors.ENDC)
|
||||
for i, file in enumerate(files, 1):
|
||||
print(f"{i}. {file}")
|
||||
choice = int(input("Select a file to import: "))
|
||||
return files[choice - 1]
|
||||
|
||||
def import_custom_formats(source_config):
|
||||
headers = {"X-Api-Key": source_config['api_key']}
|
||||
get_url = f"{source_config['base_url']}/api/v3/customformat"
|
||||
try:
|
||||
response = requests.get(get_url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
existing_formats = response.json()
|
||||
existing_names_to_id = {format['name']: format['id'] for format in existing_formats}
|
||||
|
||||
selected_file = select_file('./custom_formats')
|
||||
added_count, updated_count = 0, 0
|
||||
|
||||
with open(os.path.join('./custom_formats', selected_file), 'r') as import_file:
|
||||
import_formats = json.load(import_file)
|
||||
|
||||
print()
|
||||
|
||||
for format in import_formats:
|
||||
format_name = format['name']
|
||||
if format_name in existing_names_to_id:
|
||||
format_id = existing_names_to_id[format_name]
|
||||
put_url = f"{source_config['base_url']}/api/v3/customformat/{format_id}"
|
||||
response = requests.put(put_url, json=format, headers=headers)
|
||||
if response.status_code in [200, 201, 202]:
|
||||
print(Colors.WARNING + f"Updating custom format '{format_name}': " + Colors.ENDC, end='')
|
||||
print_success("SUCCESS")
|
||||
updated_count += 1
|
||||
else:
|
||||
print_error(f"Updating custom format '{format_name}': FAIL")
|
||||
print(response.content.decode())
|
||||
|
||||
else:
|
||||
post_url = f"{source_config['base_url']}/api/v3/customformat"
|
||||
response = requests.post(post_url, json=format, headers=headers)
|
||||
if response.status_code in [200, 201]:
|
||||
print(Colors.OKBLUE + f"Adding custom format '{format_name}': " + Colors.ENDC, end='')
|
||||
print_success("SUCCESS")
|
||||
added_count += 1
|
||||
else:
|
||||
print_error(f"Adding custom format '{format_name}': FAIL")
|
||||
print(response.content.decode())
|
||||
|
||||
print()
|
||||
print_success(f"Successfully added {added_count} custom formats, updated {updated_count} custom formats.")
|
||||
|
||||
else:
|
||||
print_error(f"Failed to retrieve existing custom formats from {get_url}! (HTTP {response.status_code})")
|
||||
print(response.content.decode())
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print_connection_error()
|
||||
|
||||
def import_quality_profiles(source_config):
|
||||
headers = {"X-Api-Key": source_config['api_key']}
|
||||
try:
|
||||
cf_import_sync(source_config)
|
||||
|
||||
profile_dir = './profiles'
|
||||
profiles = [f for f in os.listdir(profile_dir) if f.endswith('.json')]
|
||||
|
||||
print()
|
||||
print(Colors.HEADER + "Available Profiles:" + Colors.ENDC)
|
||||
for i, profile in enumerate(profiles, 1):
|
||||
print(f"{i}. {profile}")
|
||||
|
||||
print()
|
||||
selection = input("Please enter the number of the profile you want to import: ")
|
||||
try:
|
||||
selected_file = profiles[int(selection) - 1]
|
||||
except (ValueError, IndexError):
|
||||
print_error("Invalid selection, please enter a valid number.")
|
||||
return
|
||||
|
||||
with open(os.path.join(profile_dir, selected_file), 'r') as file:
|
||||
try:
|
||||
quality_profiles = json.load(file)
|
||||
except json.JSONDecodeError as e:
|
||||
print_error(f"Error loading selected profile: {e}")
|
||||
return
|
||||
|
||||
for profile in quality_profiles:
|
||||
existing_format_names = set()
|
||||
if 'formatItems' in profile:
|
||||
for format_item in profile['formatItems']:
|
||||
format_name = format_item.get('name')
|
||||
if format_name:
|
||||
existing_format_names.add(format_name)
|
||||
if format_name in source_config['custom_formats']:
|
||||
format_item['format'] = source_config['custom_formats'][format_name]
|
||||
|
||||
for format_name, format_id in source_config['custom_formats'].items():
|
||||
if format_name not in existing_format_names:
|
||||
profile.setdefault('formatItems', []).append({
|
||||
"format": format_id,
|
||||
"name": format_name,
|
||||
"score": 0
|
||||
})
|
||||
|
||||
post_url = f"{source_config['base_url']}/api/v3/qualityprofile"
|
||||
response = requests.post(post_url, json=profile, headers=headers)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
print_success(f"Successfully added Quality Profile {profile['name']}")
|
||||
elif response.status_code == 409:
|
||||
print_error(f"Failed to add Quality Profile {profile['name']} due to a naming conflict. Quality profile names must be unique. (HTTP {response.status_code})")
|
||||
else:
|
||||
try:
|
||||
errors = response.json()
|
||||
message = errors.get("message", "No Message Provided")
|
||||
print_error(f"Failed to add Quality Profile {profile['name']}! (HTTP {response.status_code})")
|
||||
print(message)
|
||||
except json.JSONDecodeError:
|
||||
print_error("Failed to parse error message:")
|
||||
print(response.text)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print_connection_error()
|
||||
|
||||
def cf_import_sync(source_config):
|
||||
headers = {"X-Api-Key": source_config['api_key']}
|
||||
custom_format_url = f"{source_config['base_url']}/api/v3/customformat"
|
||||
try:
|
||||
response = requests.get(custom_format_url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
source_config['custom_formats'] = {format['name']: format['id'] for format in data}
|
||||
elif response.status_code == 401:
|
||||
print_error("Authentication error: Invalid API key. Terminating program.")
|
||||
exit(1)
|
||||
else:
|
||||
print_error(f"Failed to retrieve custom formats! (HTTP {response.status_code})")
|
||||
print(response.content.decode())
|
||||
exit(1)
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print_connection_error()
|
||||
exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
user_choice = get_user_choice()
|
||||
source_config = get_app_config(user_choice)
|
||||
import_choice = get_import_choice()
|
||||
|
||||
if import_choice == "1":
|
||||
import_custom_formats(source_config)
|
||||
elif import_choice == "2":
|
||||
import_quality_profiles(source_config)
|
||||
@@ -1,60 +0,0 @@
|
||||
import json
|
||||
import requests
|
||||
|
||||
# Define constants
|
||||
base_url = "http://localhost:7878" # Update to your Radarr URL
|
||||
api_key = "API_GOES_HERE" # Update to your Radarr API Key
|
||||
|
||||
# Define headers
|
||||
headers = {"X-Api-Key": api_key}
|
||||
|
||||
def get_existing_formats():
|
||||
get_url = f"{base_url}/api/v3/customformat"
|
||||
print(f"Getting existing formats from {get_url}")
|
||||
response = requests.get(get_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
with open('temp_cf.json', 'w') as temp_file:
|
||||
json.dump(response.json(), temp_file)
|
||||
else:
|
||||
print(f"Failed to retrieve existing custom formats from {get_url}! (HTTP {response.status_code})")
|
||||
print("Response Content: \n", response.content.decode())
|
||||
exit(1)
|
||||
|
||||
def import_custom_formats():
|
||||
with open('temp_cf.json', 'r') as temp_file:
|
||||
existing_formats = json.load(temp_file)
|
||||
existing_names_to_id = {format['name']: format['id'] for format in existing_formats}
|
||||
|
||||
with open('custom_formats/cf.json', 'r') as import_file:
|
||||
import_formats = json.load(import_file)
|
||||
|
||||
for format in import_formats:
|
||||
format_name = format['name']
|
||||
if format_name in existing_names_to_id:
|
||||
format_id = existing_names_to_id[format_name]
|
||||
put_url = f"{base_url}/api/v3/customformat/{format_id}"
|
||||
print(f"Updating existing format {format_name} using PUT at {put_url}")
|
||||
format['id'] = format_id # Include the id in the request body
|
||||
response = requests.put(put_url, json=format, headers=headers)
|
||||
|
||||
if response.status_code in [200, 201, 202]:
|
||||
print(f"Successfully updated custom format {format_name}! (HTTP {response.status_code})")
|
||||
else:
|
||||
print(f"Failed to update custom format {format_name} at {put_url}! (HTTP {response.status_code})")
|
||||
print("Response Content: \n", response.content.decode())
|
||||
|
||||
else:
|
||||
post_url = f"{base_url}/api/v3/customformat"
|
||||
print(f"Creating new format {format_name} using POST at {post_url}")
|
||||
response = requests.post(post_url, json=format, headers=headers)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
print(f"Successfully created custom format {format_name}! (HTTP {response.status_code})")
|
||||
else:
|
||||
print(f"Failed to create custom format {format_name} at {post_url}! (HTTP {response.status_code})")
|
||||
print("Response Content: \n", response.content.decode())
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_existing_formats()
|
||||
import_custom_formats()
|
||||
@@ -1,115 +0,0 @@
|
||||
import json
|
||||
import requests
|
||||
import os # For deleting the temporary file
|
||||
|
||||
# Define constants
|
||||
base_url = "http://localhost:7878" # Update to your Radarr URL
|
||||
api_key = "API_GOES_HERE" # Update to your Radarr API Key
|
||||
|
||||
# Define headers
|
||||
params = {"apikey": api_key}
|
||||
headers = {"X-Api-Key": api_key}
|
||||
|
||||
def cf_import_sync():
|
||||
custom_format_url = f"{base_url}/api/v3/customformat"
|
||||
response = requests.get(custom_format_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
with open('custom_formats.json', 'w') as file:
|
||||
json.dump(data, file, indent=4)
|
||||
print("Custom Formats have been saved to 'custom_formats.json'")
|
||||
return True
|
||||
else:
|
||||
print(f"Failed to retrieve custom formats! (HTTP {response.status_code})")
|
||||
print("Response Content: ", response.content.decode('utf-8'))
|
||||
return False
|
||||
|
||||
|
||||
def import_qf():
|
||||
# Call cf_import_sync first
|
||||
cf_import_sync()
|
||||
|
||||
profile_dir = './profiles'
|
||||
profiles = [f for f in os.listdir(profile_dir) if f.endswith('.json')]
|
||||
|
||||
# Prompt user to select a profile
|
||||
print("Available Profiles:")
|
||||
for i, profile in enumerate(profiles, 1):
|
||||
print(f"{i}. {profile}")
|
||||
|
||||
selection = input("Please enter the number of the profile you want to import: ")
|
||||
|
||||
try:
|
||||
selected_file = profiles[int(selection) - 1]
|
||||
except (ValueError, IndexError):
|
||||
print("Invalid selection, please enter a valid number.")
|
||||
return
|
||||
|
||||
# Load the selected profile
|
||||
with open(os.path.join(profile_dir, selected_file), 'r') as file:
|
||||
try:
|
||||
quality_profiles = json.load(file)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error loading selected profile: {e}")
|
||||
return
|
||||
|
||||
# Load custom formats
|
||||
try:
|
||||
with open('custom_formats.json', 'r') as file:
|
||||
custom_formats_data = json.load(file)
|
||||
custom_formats = {format['name']: format['id'] for format in custom_formats_data}
|
||||
except Exception as e:
|
||||
print(f"Failed to load custom formats! Error: {e}")
|
||||
return
|
||||
|
||||
# Process each profile and send requests
|
||||
for profile in quality_profiles:
|
||||
existing_format_names = set()
|
||||
if 'formatItems' in profile:
|
||||
for format_item in profile['formatItems']:
|
||||
format_name = format_item.get('name')
|
||||
if format_name:
|
||||
existing_format_names.add(format_name)
|
||||
if format_name in custom_formats:
|
||||
format_item['format'] = custom_formats[format_name]
|
||||
|
||||
for format_name, format_id in custom_formats.items():
|
||||
if format_name not in existing_format_names:
|
||||
profile.setdefault('formatItems', []).append({
|
||||
"format": format_id,
|
||||
"name": format_name,
|
||||
"score": 0
|
||||
})
|
||||
|
||||
post_url = f"{base_url}/api/v3/qualityprofile"
|
||||
response = requests.post(post_url, json=profile, params=params, headers=headers)
|
||||
|
||||
if response.status_code in [200, 201]:
|
||||
print(f"Successfully added Quality Profile {profile['name']}! (HTTP {response.status_code})")
|
||||
else:
|
||||
try:
|
||||
# Assuming the response is JSON, parse it
|
||||
errors = response.json()
|
||||
|
||||
# Extract relevant information from the error message
|
||||
message = errors.get("message", "No Message Provided")
|
||||
description = errors.get("description", "No Description Provided")
|
||||
|
||||
# Format and print the error message
|
||||
print(f"Failed to add Quality Profile {profile['name']}! (HTTP {response.status_code})")
|
||||
print(f"Error Message: {message}")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
# If response is not JSON, print the whole response
|
||||
print("Failed to parse error message:")
|
||||
print(response.text)
|
||||
try:
|
||||
os.remove('custom_formats.json')
|
||||
except FileNotFoundError:
|
||||
pass # File already deleted or does not exist
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import_qf()
|
||||
1020
profiles/Transparent - HD Fallback (Radarr).json
Normal file
1020
profiles/Transparent - HD Fallback (Radarr).json
Normal file
File diff suppressed because it is too large
Load Diff
1020
profiles/Transparent - Remux Fallback (Radarr).json
Normal file
1020
profiles/Transparent - Remux Fallback (Radarr).json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
[]
|
||||
Reference in New Issue
Block a user