mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-24 11:41:03 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c764f993c | ||
|
|
fc8196dc10 | ||
|
|
c39d477deb | ||
|
|
cbdc4a0c8e | ||
|
|
972d3bc1fc |
232
README.md
232
README.md
@@ -17,27 +17,20 @@ Profilarr is a Python-based tool designed to add import/export/sync functionalit
|
|||||||
|
|
||||||
### 📦 Dependencies
|
### 📦 Dependencies
|
||||||
|
|
||||||
- `requests` (Install using `pip install requests`)
|
- run `pip install -r requirements.txt` to install dependencies.
|
||||||
|
|
||||||
### Initial Setup
|
### Initial Setup
|
||||||
|
|
||||||
1. Download the latest Profilarr package from the release section.
|
1. Download the latest Profilarr package from the release section.
|
||||||
2. Extract its contents into a folder.
|
2. Extract its contents into a folder.
|
||||||
3. Open the `config.json` file in a text editor.
|
3. Open the `config.yml` file in a text editor.
|
||||||
- Add your Radarr / Sonarr API key and modify the base URL as necessary.
|
- Add the URL and API key to the master instances of Radarr / Sonarr.
|
||||||
- If importing / exporting, only change the master installation's API key and base URL.
|
- If syncing, add the URL, API key and a name to each extra instance of Radarr / Sonarr.
|
||||||
- If syncing, add the API keys and base URLs of all instances you want to sync.
|
- If exporting, adjust the `export_path` to your desired export location.
|
||||||
- The master install will be the one that all other instances sync to.
|
|
||||||
4. Save the changes.
|
4. Save the changes.
|
||||||
|
|
||||||
## 🚀 Usage
|
## 🚀 Usage
|
||||||
|
|
||||||
### Exporting
|
|
||||||
|
|
||||||
1. Run `python exportarr.py` in your command line interface.
|
|
||||||
2. Follow the on-screen prompts to select the app (Radarr or Sonarr) and the data (Custom Formats or Quality Profiles) you want to export.
|
|
||||||
3. Exported data will be saved in respective directories within the tool's folder.
|
|
||||||
|
|
||||||
### Importing
|
### Importing
|
||||||
|
|
||||||
1. Run `python importarr.py` in your command line interface.
|
1. Run `python importarr.py` in your command line interface.
|
||||||
@@ -45,16 +38,225 @@ Profilarr is a Python-based tool designed to add import/export/sync functionalit
|
|||||||
3. Choose the specific file for Custom Formats or select a profile for Quality Profiles.
|
3. Choose the specific file for Custom Formats or select a profile for Quality Profiles.
|
||||||
4. The data will be imported to your selected Radarr or Sonarr installation.
|
4. The data will be imported to your selected Radarr or Sonarr installation.
|
||||||
|
|
||||||
|
#### Custom Format Import Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PS Z:\Profilarr> py importarr.py
|
||||||
|
Available instances to import to:
|
||||||
|
1. Sonarr [Master]
|
||||||
|
2. Radarr [Master]
|
||||||
|
3. Sonarr [4k-sonarr]
|
||||||
|
4. Radarr [4k-radarr]
|
||||||
|
Enter the number of the instance to import to: 4
|
||||||
|
|
||||||
|
|
||||||
|
Choose what to import:
|
||||||
|
1. Custom Formats
|
||||||
|
2. Quality Profiles
|
||||||
|
Enter your choice (1/2): 1
|
||||||
|
|
||||||
|
Available files:
|
||||||
|
1. Custom Formats (Radarr).json
|
||||||
|
Select a file to import (or 'all' for all files): 1
|
||||||
|
|
||||||
|
Adding custom format 'D-Z0N3': SUCCESS
|
||||||
|
Adding custom format 'DON': SUCCESS
|
||||||
|
Adding custom format 'EbP': SUCCESS
|
||||||
|
Adding custom format 'Geek': SUCCESS
|
||||||
|
Adding custom format 'TayTo': SUCCESS
|
||||||
|
Adding custom format 'ZQ': SUCCESS
|
||||||
|
Adding custom format 'VietHD': SUCCESS
|
||||||
|
Adding custom format 'CtrlHD': SUCCESS
|
||||||
|
Adding custom format 'HiFi': SUCCESS
|
||||||
|
Adding custom format 'FoRM': SUCCESS
|
||||||
|
Adding custom format 'HiDt': SUCCESS
|
||||||
|
Adding custom format 'SA89': SUCCESS
|
||||||
|
...
|
||||||
|
|
||||||
|
Successfully added 0 custom formats, updated 131 custom formats.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quality Profile Import Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PS Z:\Profilarr> py importarr.py
|
||||||
|
Available instances to import to:
|
||||||
|
1. Sonarr [Master]
|
||||||
|
2. Radarr [Master]
|
||||||
|
3. Sonarr [4k-sonarr]
|
||||||
|
4. Radarr [4k-radarr]
|
||||||
|
Enter the number of the instance to import to: 4
|
||||||
|
|
||||||
|
|
||||||
|
Choose what to import:
|
||||||
|
1. Custom Formats
|
||||||
|
2. Quality Profiles
|
||||||
|
Enter your choice (1/2): 2
|
||||||
|
|
||||||
|
Available files:
|
||||||
|
1. 1080p Balanced (Radarr).json
|
||||||
|
2. 1080p Balanced (Single Grab) (Radarr).json
|
||||||
|
3. 1080p h265 Balanced (Radarr).json
|
||||||
|
4. 1080p h265 Balanced (Single Grab) (Radarr).json
|
||||||
|
5. 1080p Optimal (Radarr).json
|
||||||
|
6. 1080p Optimal (Single Grab) (Radarr).json
|
||||||
|
7. 1080p Transparent (Double Grab) (Radarr).json
|
||||||
|
8. 1080p Transparent (Radarr).json
|
||||||
|
9. 1080p Transparent (Single Grab) (Radarr).json
|
||||||
|
10. 2160p Optimal (Radarr).json
|
||||||
|
11. 2160p Optimal (Single Grab) (Radarr).json
|
||||||
|
Select a file to import (or 'all' for all files): all
|
||||||
|
|
||||||
|
Successfully added Quality Profile 1080p Balanced
|
||||||
|
Successfully added Quality Profile 1080p Balanced (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p h265 Balanced
|
||||||
|
Successfully added Quality Profile 1080p h265 Balanced (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p Optimal
|
||||||
|
Successfully added Quality Profile 1080p Optimal (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p Transparent (Double Grab)
|
||||||
|
Successfully added Quality Profile 1080p Transparent
|
||||||
|
Successfully added Quality Profile 1080p Transparent (Single Grab)
|
||||||
|
Successfully added Quality Profile 2160p Optimal
|
||||||
|
Successfully added Quality Profile 2160p Optimal (Single Grab)
|
||||||
|
PS Z:\Profilarr>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exporting
|
||||||
|
|
||||||
|
1. Run `python exportarr.py` in your command line interface.
|
||||||
|
2. Choose the instance you want to export from.
|
||||||
|
3. Choose the data you want to export.
|
||||||
|
4. The data will be exported to `exports/{instance_type}/{instance_name}/{data_type}`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PS Z:\Profilarr> py exportarr.py
|
||||||
|
Available sources to export from:
|
||||||
|
1. Sonarr [Master]
|
||||||
|
2. Radarr [Master]
|
||||||
|
3. Sonarr [4k-sonarr]
|
||||||
|
4. Radarr [4k-radarr]
|
||||||
|
Enter the number of the app to export from: 2
|
||||||
|
|
||||||
|
Choose what to export:
|
||||||
|
1. Custom Formats
|
||||||
|
2. Quality Profiles
|
||||||
|
3. Both
|
||||||
|
Enter your choice (1/2/3): 3
|
||||||
|
|
||||||
|
Attempting to access Radarr at http://localhost:7878
|
||||||
|
Found 131 custom formats.
|
||||||
|
- D-Z0N3
|
||||||
|
- DON
|
||||||
|
- EbP
|
||||||
|
- Geek
|
||||||
|
- TayTo
|
||||||
|
- ZQ
|
||||||
|
- VietHD
|
||||||
|
- CtrlHD
|
||||||
|
- HiFi
|
||||||
|
- FoRM
|
||||||
|
... and 121 more.
|
||||||
|
Saved to './exports\radarr\master\custom_formats\Custom Formats (Radarr).json'
|
||||||
|
|
||||||
|
Attempting to access Radarr at http://localhost:7878
|
||||||
|
Found 13 quality profiles.
|
||||||
|
- 1080p Optimal
|
||||||
|
- 2160p Optimal
|
||||||
|
- 1080p Balanced
|
||||||
|
- 1080p Transparent
|
||||||
|
- 1080p Transparent (Double Grab)
|
||||||
|
- 1080p Transparent (Single Grab)
|
||||||
|
- 1080p Balanced (Single Grab)
|
||||||
|
- 1080p h265 Balanced
|
||||||
|
- 1080p h265 Balanced (Single Grab)
|
||||||
|
- 1080p x265 HDR Transparent
|
||||||
|
... and 3 more.
|
||||||
|
Saved to 'exports\radarr\master\profiles'
|
||||||
|
|
||||||
|
PS Z:\Profilarr>
|
||||||
|
```
|
||||||
|
|
||||||
### Syncing
|
### Syncing
|
||||||
|
|
||||||
1. Run `python syncarr.py` in your command line interface.
|
1. Run `python syncarr.py` in your command line interface.
|
||||||
2. The script will automatically export data from the master instance and import it to all other instances specified in `config.json`.
|
2. The script will automatically export data from the master instance and import it to all other instances specified in `config.json`.
|
||||||
3. This feature is designed to manage multiple Radarr/Sonarr instances, syncing profiles and formats seamlessly.
|
3. This feature is designed to manage multiple Radarr/Sonarr instances, syncing profiles and formats seamlessly.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PS Z:\Profilarr> py syncarr.py
|
||||||
|
Select the app you want to sync:
|
||||||
|
1. Radarr
|
||||||
|
2. Sonarr
|
||||||
|
Enter your choice (1 or 2): 2
|
||||||
|
Attempting to access Sonarr at http://localhost:8989
|
||||||
|
Found 135 custom formats.
|
||||||
|
- D-Z0N3
|
||||||
|
- DON
|
||||||
|
- EbP
|
||||||
|
- Geek
|
||||||
|
- TayTo
|
||||||
|
- ZQ
|
||||||
|
- VietHD
|
||||||
|
- CtrlHD
|
||||||
|
- HiFi
|
||||||
|
- FoRM
|
||||||
|
... and 125 more.
|
||||||
|
Saved to './temp_directory/custom_formats\Custom Formats (Sonarr).json'
|
||||||
|
|
||||||
|
Attempting to access Sonarr at http://localhost:8989
|
||||||
|
Found 11 quality profiles.
|
||||||
|
- 1080p Transparent
|
||||||
|
- 2160p Optimal
|
||||||
|
- 1080p Transparent (Single Grab)
|
||||||
|
- 1080p Transparent (Double Grab)
|
||||||
|
- 1080p Balanced
|
||||||
|
- 1080p Balanced (Single Grab)
|
||||||
|
- 1080p h265 Balanced
|
||||||
|
- 1080p h265 Balanced (Single Grab)
|
||||||
|
- 1080p Optimal
|
||||||
|
- 1080p Optimal (Single Grab)
|
||||||
|
... and 1 more.
|
||||||
|
Saved to 'temp_directory\quality_profiles'
|
||||||
|
|
||||||
|
Importing to instance: 4k-sonarr
|
||||||
|
Adding custom format 'D-Z0N3': SUCCESS
|
||||||
|
Adding custom format 'DON': SUCCESS
|
||||||
|
Adding custom format 'EbP': SUCCESS
|
||||||
|
Adding custom format 'Geek': SUCCESS
|
||||||
|
Adding custom format 'TayTo': SUCCESS
|
||||||
|
Adding custom format 'ZQ': SUCCESS
|
||||||
|
Adding custom format 'VietHD': SUCCESS
|
||||||
|
Adding custom format 'CtrlHD': SUCCESS
|
||||||
|
Adding custom format 'HiFi': SUCCESS
|
||||||
|
... and 125 more.
|
||||||
|
|
||||||
|
Successfully added 135 custom formats, updated 0 custom formats.
|
||||||
|
Successfully added Quality Profile 1080p Balanced (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p Balanced
|
||||||
|
Successfully added Quality Profile 1080p h265 Balanced
|
||||||
|
Successfully added Quality Profile 1080p h265 Balanced (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p Optimal (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p Optimal
|
||||||
|
Successfully added Quality Profile 1080p Transparent (Double Grab)
|
||||||
|
Successfully added Quality Profile 1080p Transparent (Single Grab)
|
||||||
|
Successfully added Quality Profile 1080p Transparent
|
||||||
|
Successfully added Quality Profile 2160p Optimal (Single Grab)
|
||||||
|
Successfully added Quality Profile 2160p Optimal
|
||||||
|
Deleted temporary directory: ./temp_directory
|
||||||
|
PS Z:\Profilarr>
|
||||||
|
```
|
||||||
|
|
||||||
### Radarr and Sonarr Compatibility
|
### Radarr and Sonarr Compatibility
|
||||||
|
|
||||||
- Custom formats _can_ be imported and exported between Radarr and Sonarr (but might not work as expected).
|
- You are only able to import / sync files to the app that is included in the file name (e.g. `Radarr` or `Sonarr`).
|
||||||
- Quality profiles are not directly interchangeable between Radarr and Sonarr due to differences in quality source names. If you want to use the same profile in both apps, you will need to manually edit the profile's quality source names before importing it.
|
- It is possible to manually rename the files to import them to the other app, but this is not recommended.
|
||||||
|
- Custom Formats will succesfully import, but will require manual editing to work with the other app, i.e. you must adjust the quality sources to match the other app's naming scheme.
|
||||||
|
- Quality Profiles will not import at all, as they are not compatible with the other app. It is possible to import them manually by editing the json directly, but this is not recommended.
|
||||||
|
- In future, I may add a feature to automatically convert profiles between the two apps, but this is not currently a priority.
|
||||||
|
|
||||||
## 🌟 Upcoming Features
|
## 🌟 Upcoming Features
|
||||||
|
|
||||||
@@ -62,7 +264,7 @@ Profilarr is a Python-based tool designed to add import/export/sync functionalit
|
|||||||
- **User Interface (UI):** Development of a graphical user interface (GUI) for easier and more intuitive interaction with Profilarr. This UI will cater to users who prefer graphical over command-line interactions.
|
- **User Interface (UI):** Development of a graphical user interface (GUI) for easier and more intuitive interaction with Profilarr. This UI will cater to users who prefer graphical over command-line interactions.
|
||||||
- **Automatic Updates:** Implement an auto-update mechanism for Profilarr, ensuring users always have access to the latest features, improvements, and bug fixes without manual intervention.
|
- **Automatic Updates:** Implement an auto-update mechanism for Profilarr, ensuring users always have access to the latest features, improvements, and bug fixes without manual intervention.
|
||||||
|
|
||||||
# TRASH Guides
|
# TRaSH Guides
|
||||||
|
|
||||||
Some custom formats found here have been interated on from the trash guides. Credit for these goes entirely to trash, and can be found on their site here. It is not my intention to steal their work, but rather to build on it and make it more accessible to the average user through my quality profiles. Please check out their site for more information on their work.
|
Some custom formats found here have been interated on from the trash guides. Credit for these goes entirely to trash, and can be found on their site here. It is not my intention to steal their work, but rather to build on it and make it more accessible to the average user through my quality profiles. Please check out their site for more information on their work.
|
||||||
|
|
||||||
|
|||||||
38
config.json
38
config.json
@@ -1,38 +0,0 @@
|
|||||||
{
|
|
||||||
"master": {
|
|
||||||
"sonarr": {
|
|
||||||
"base_url": "http://localhost:8989",
|
|
||||||
"api_key": "API_KEY_HERE"
|
|
||||||
},
|
|
||||||
"radarr": {
|
|
||||||
"base_url": "http://localhost:7878",
|
|
||||||
"api_key": "API_KEY_HERE"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"extra_installations": {
|
|
||||||
"radarr": [
|
|
||||||
{
|
|
||||||
"name": "extra_radarr1",
|
|
||||||
"base_url": "http://localhost:7788",
|
|
||||||
"api_key": "API_KEY_HERE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extra_radarr2",
|
|
||||||
"base_url": "http://localhost:7789",
|
|
||||||
"api_key": "API_KEY_HERE"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sonarr": [
|
|
||||||
{
|
|
||||||
"name": "extra_sonarr1",
|
|
||||||
"base_url": "http://localhost:8988",
|
|
||||||
"api_key": "API_KEY_HERE"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extra_sonarr2",
|
|
||||||
"base_url": "http://localhost:8987",
|
|
||||||
"api_key": "API_KEY_HERE"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
config.yml
Normal file
20
config.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
instances:
|
||||||
|
master:
|
||||||
|
sonarr:
|
||||||
|
base_url: "http://localhost:8989"
|
||||||
|
api_key: "API_KEY"
|
||||||
|
radarr:
|
||||||
|
base_url: "http://localhost:7878"
|
||||||
|
api_key: "API_KEY"
|
||||||
|
extras:
|
||||||
|
sonarr:
|
||||||
|
- name: "4k-sonarr"
|
||||||
|
base_url: "http://localhost:8998"
|
||||||
|
api_key: "API_KEY"
|
||||||
|
radarr:
|
||||||
|
- name: "4k-radarr"
|
||||||
|
base_url: "http://localhost:7887"
|
||||||
|
api_key: "API_KEY"
|
||||||
|
|
||||||
|
settings:
|
||||||
|
export_path: "./exports"
|
||||||
71
exportarr.py
71
exportarr.py
@@ -1,7 +1,8 @@
|
|||||||
import json
|
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
|
||||||
# ANSI escape sequences for colors
|
# ANSI escape sequences for colors
|
||||||
class Colors:
|
class Colors:
|
||||||
@@ -15,16 +16,39 @@ class Colors:
|
|||||||
UNDERLINE = '\033[4m'
|
UNDERLINE = '\033[4m'
|
||||||
|
|
||||||
# Load configuration for main app
|
# Load configuration for main app
|
||||||
with open('config.json', 'r') as config_file:
|
with open('config.yml', 'r') as config_file:
|
||||||
config = json.load(config_file)['master']
|
config = yaml.safe_load(config_file)
|
||||||
|
master_config = config['instances']['master']
|
||||||
|
export_base_path = config['settings']['export_path']
|
||||||
|
|
||||||
def get_user_choice():
|
def get_user_choice():
|
||||||
choice = input("Enter an app to export from (radarr/sonarr): ").lower()
|
sources = []
|
||||||
while choice not in ["radarr", "sonarr"]:
|
print(Colors.HEADER + "Available sources to export from:" + Colors.ENDC)
|
||||||
print(Colors.FAIL + "Invalid input. Please enter either 'radarr' or 'sonarr'." + Colors.ENDC)
|
|
||||||
choice = input("Enter the source (radarr/sonarr): ").lower()
|
# Add master installations
|
||||||
|
for app in master_config:
|
||||||
|
sources.append((app, f"{app.capitalize()} [Master]", "master"))
|
||||||
|
|
||||||
|
# Add extra installations
|
||||||
|
if "extras" in config['instances']:
|
||||||
|
for app, instances in config['instances']['extras'].items():
|
||||||
|
for install in instances:
|
||||||
|
sources.append((app, f"{app.capitalize()} [{install['name']}]", install['name']))
|
||||||
|
|
||||||
|
# Display sources with numbers
|
||||||
|
for idx, (app, name, _) in enumerate(sources, start=1):
|
||||||
|
print(f"{idx}. {name}")
|
||||||
|
|
||||||
|
# User selection
|
||||||
|
choice = input("Enter the number of the app to export from: ").strip()
|
||||||
|
while not choice.isdigit() or int(choice) < 1 or int(choice) > len(sources):
|
||||||
|
print(Colors.FAIL + "Invalid input. Please enter a valid number." + Colors.ENDC)
|
||||||
|
choice = input("Enter the number of the app to export from: ").strip()
|
||||||
|
|
||||||
|
selected_app, instance_name = sources[int(choice) - 1][0], sources[int(choice) - 1][2]
|
||||||
print()
|
print()
|
||||||
return choice
|
return selected_app, instance_name
|
||||||
|
|
||||||
|
|
||||||
def get_export_choice():
|
def get_export_choice():
|
||||||
print(Colors.HEADER + "Choose what to export:" + Colors.ENDC)
|
print(Colors.HEADER + "Choose what to export:" + Colors.ENDC)
|
||||||
@@ -39,7 +63,7 @@ def get_export_choice():
|
|||||||
return choice
|
return choice
|
||||||
|
|
||||||
def get_app_config(source):
|
def get_app_config(source):
|
||||||
app_config = config[source]
|
app_config = master_config[source]
|
||||||
return app_config['base_url'], app_config['api_key']
|
return app_config['base_url'], app_config['api_key']
|
||||||
|
|
||||||
def sanitize_filename(filename):
|
def sanitize_filename(filename):
|
||||||
@@ -70,8 +94,10 @@ def ensure_directory_exists(directory):
|
|||||||
os.makedirs(directory)
|
os.makedirs(directory)
|
||||||
print(Colors.OKBLUE + f"Created directory: {directory}" + Colors.ENDC)
|
print(Colors.OKBLUE + f"Created directory: {directory}" + Colors.ENDC)
|
||||||
|
|
||||||
def export_cf(source, save_path='./custom_formats'):
|
def export_cf(source, instance_name, save_path=None):
|
||||||
ensure_directory_exists(save_path) # Ensure the directory exists with the given save_path
|
if save_path is None:
|
||||||
|
save_path = os.path.join(export_base_path, source, instance_name, 'custom_formats')
|
||||||
|
ensure_directory_exists(save_path)
|
||||||
|
|
||||||
base_url, api_key = get_app_config(source)
|
base_url, api_key = get_app_config(source)
|
||||||
headers = {"X-Api-Key": api_key}
|
headers = {"X-Api-Key": api_key}
|
||||||
@@ -93,7 +119,7 @@ def export_cf(source, save_path='./custom_formats'):
|
|||||||
custom_format.pop('id', None)
|
custom_format.pop('id', None)
|
||||||
saved_formats.append(custom_format['name'])
|
saved_formats.append(custom_format['name'])
|
||||||
|
|
||||||
file_path = f'{save_path}/Custom Formats ({source.capitalize()}).json'
|
file_path = os.path.join(save_path, f'Custom Formats ({source.capitalize()}).json')
|
||||||
with open(file_path, 'w') as f:
|
with open(file_path, 'w') as f:
|
||||||
json.dump(data, f, indent=4)
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
@@ -108,8 +134,10 @@ def export_cf(source, save_path='./custom_formats'):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def export_qf(source, save_path='./profiles'):
|
def export_qf(source, instance_name, save_path=None):
|
||||||
ensure_directory_exists(save_path) # Ensure the directory exists with the given save_path
|
if save_path is None:
|
||||||
|
save_path = os.path.join(export_base_path, source, instance_name, 'profiles')
|
||||||
|
ensure_directory_exists(save_path)
|
||||||
|
|
||||||
base_url, api_key = get_app_config(source)
|
base_url, api_key = get_app_config(source)
|
||||||
headers = {"X-Api-Key": api_key}
|
headers = {"X-Api-Key": api_key}
|
||||||
@@ -124,9 +152,6 @@ def export_qf(source, save_path='./profiles'):
|
|||||||
quality_profiles = response.json()
|
quality_profiles = response.json()
|
||||||
print(Colors.OKGREEN + f"Found {len(quality_profiles)} quality profiles." + Colors.ENDC)
|
print(Colors.OKGREEN + f"Found {len(quality_profiles)} quality profiles." + Colors.ENDC)
|
||||||
|
|
||||||
if not os.path.exists('./profiles'):
|
|
||||||
os.makedirs('./profiles')
|
|
||||||
|
|
||||||
saved_profiles = []
|
saved_profiles = []
|
||||||
for profile in quality_profiles:
|
for profile in quality_profiles:
|
||||||
profile.pop('id', None)
|
profile.pop('id', None)
|
||||||
@@ -134,12 +159,12 @@ def export_qf(source, save_path='./profiles'):
|
|||||||
profile_name = sanitize_filename(profile_name)
|
profile_name = sanitize_filename(profile_name)
|
||||||
profile_filename = f"{profile_name} ({source.capitalize()}).json"
|
profile_filename = f"{profile_name} ({source.capitalize()}).json"
|
||||||
profile_filepath = os.path.join(save_path, profile_filename)
|
profile_filepath = os.path.join(save_path, profile_filename)
|
||||||
saved_profiles.append(profile_name)
|
|
||||||
|
|
||||||
with open(profile_filepath, 'w') as file:
|
with open(profile_filepath, 'w') as file:
|
||||||
json.dump([profile], file, indent=4)
|
json.dump([profile], file, indent=4)
|
||||||
|
saved_profiles.append(profile_name) # Add the profile name to the list
|
||||||
|
|
||||||
print_saved_items(saved_profiles, "Quality Profiles")
|
print_saved_items(saved_profiles, "Quality Profiles")
|
||||||
print(Colors.OKGREEN + f"Saved to '{profile_filepath}'" + Colors.ENDC)
|
print(Colors.OKGREEN + f"Saved to '{os.path.normpath(save_path)}'" + Colors.ENDC) # Normalize the path
|
||||||
print()
|
print()
|
||||||
else:
|
else:
|
||||||
handle_response_errors(response)
|
handle_response_errors(response)
|
||||||
@@ -149,10 +174,10 @@ def export_qf(source, save_path='./profiles'):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
user_choice = get_user_choice()
|
user_choice, instance_name = get_user_choice()
|
||||||
export_choice = get_export_choice()
|
export_choice = get_export_choice()
|
||||||
|
|
||||||
if export_choice in ["1", "3"]:
|
if export_choice in ["1", "3"]:
|
||||||
export_cf(user_choice)
|
export_cf(user_choice, instance_name)
|
||||||
if export_choice in ["2", "3"]:
|
if export_choice in ["2", "3"]:
|
||||||
export_qf(user_choice)
|
export_qf(user_choice, instance_name)
|
||||||
198
importarr.py
198
importarr.py
@@ -1,6 +1,8 @@
|
|||||||
import json
|
|
||||||
import requests
|
import requests
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
|
||||||
# ANSI escape sequences for colors
|
# ANSI escape sequences for colors
|
||||||
class Colors:
|
class Colors:
|
||||||
@@ -14,8 +16,9 @@ class Colors:
|
|||||||
UNDERLINE = '\033[4m'
|
UNDERLINE = '\033[4m'
|
||||||
|
|
||||||
# Load configuration for main app
|
# Load configuration for main app
|
||||||
with open('config.json', 'r') as config_file:
|
with open('config.yml', 'r') as config_file:
|
||||||
config = json.load(config_file)
|
config = yaml.safe_load(config_file)
|
||||||
|
master_config = config['instances']['master']
|
||||||
|
|
||||||
def print_success(message):
|
def print_success(message):
|
||||||
print(Colors.OKGREEN + message + Colors.ENDC)
|
print(Colors.OKGREEN + message + Colors.ENDC)
|
||||||
@@ -27,11 +30,32 @@ def print_connection_error():
|
|||||||
print(Colors.FAIL + "Failed to connect to the service! Please check if it's running and accessible." + Colors.ENDC)
|
print(Colors.FAIL + "Failed to connect to the service! Please check if it's running and accessible." + Colors.ENDC)
|
||||||
|
|
||||||
def get_user_choice():
|
def get_user_choice():
|
||||||
choice = input("Enter the app you want to import to (radarr/sonarr): ").lower()
|
sources = []
|
||||||
while choice not in ["radarr", "sonarr"]:
|
print(Colors.HEADER + "Available instances to import to:" + Colors.ENDC)
|
||||||
print_error("Invalid input. Please enter either 'radarr' or 'sonarr'.")
|
|
||||||
choice = input("Enter the source (radarr/sonarr): ").lower()
|
# Add master installations
|
||||||
return choice
|
for app in master_config:
|
||||||
|
sources.append((app, f"{app.capitalize()} [Master]"))
|
||||||
|
|
||||||
|
# Add extra installations
|
||||||
|
if "extras" in config['instances']:
|
||||||
|
for app, instances in config['instances']['extras'].items():
|
||||||
|
for install in instances:
|
||||||
|
sources.append((app, f"{app.capitalize()} [{install['name']}]"))
|
||||||
|
|
||||||
|
# Display sources with numbers
|
||||||
|
for idx, (app, name) in enumerate(sources, start=1):
|
||||||
|
print(f"{idx}. {name}")
|
||||||
|
|
||||||
|
# User selection
|
||||||
|
choice = input("Enter the number of the instance to import to: ").strip()
|
||||||
|
while not choice.isdigit() or int(choice) < 1 or int(choice) > len(sources):
|
||||||
|
print_error("Invalid input. Please enter a valid number.")
|
||||||
|
choice = input("Enter the number of the instance to import to: ").strip()
|
||||||
|
|
||||||
|
selected_app, selected_name = sources[int(choice) - 1]
|
||||||
|
print()
|
||||||
|
return selected_app, selected_name
|
||||||
|
|
||||||
def get_import_choice():
|
def get_import_choice():
|
||||||
print()
|
print()
|
||||||
@@ -44,67 +68,93 @@ def get_import_choice():
|
|||||||
choice = input("Enter your choice (1/2): ").strip()
|
choice = input("Enter your choice (1/2): ").strip()
|
||||||
return choice
|
return choice
|
||||||
|
|
||||||
def get_app_config(source):
|
def get_app_config(app_name, instance_name):
|
||||||
return config['master'][source]
|
if instance_name.endswith("[Master]"):
|
||||||
|
return master_config[app_name]
|
||||||
|
else:
|
||||||
|
instance_name = instance_name.replace(f"{app_name.capitalize()} [", "").replace("]", "")
|
||||||
|
extras = config['instances']['extras'].get(app_name, [])
|
||||||
|
for instance in extras:
|
||||||
|
if instance['name'] == instance_name:
|
||||||
|
return instance
|
||||||
|
raise ValueError(f"Configuration for {app_name} - {instance_name} not found.")
|
||||||
|
|
||||||
|
def select_file(directory, app_name, sync_mode=False):
|
||||||
|
app_name = app_name.lower()
|
||||||
|
files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f)) and app_name in f.lower()]
|
||||||
|
if not files:
|
||||||
|
print_error(f"No files found for {app_name.capitalize()} in {directory}.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if sync_mode:
|
||||||
|
# Automatically select all files in sync mode
|
||||||
|
return files
|
||||||
|
|
||||||
def select_file(directory):
|
|
||||||
files = [f for f in os.listdir(directory) if os.path.isfile(os.path.join(directory, f))]
|
|
||||||
print()
|
print()
|
||||||
print(Colors.OKBLUE + "Available files:" + Colors.ENDC)
|
print(Colors.OKBLUE + "Available files:" + Colors.ENDC)
|
||||||
for i, file in enumerate(files, 1):
|
for i, file in enumerate(files, 1):
|
||||||
print(f"{i}. {file}")
|
print(f"{i}. {file}")
|
||||||
choice = int(input("Select a file to import: "))
|
|
||||||
return files[choice - 1]
|
|
||||||
|
|
||||||
def import_custom_formats(source_config, import_path='./custom_formats', auto_select_file=False):
|
choice = input("Select a file to import (or 'all' for all files): ").strip()
|
||||||
|
print()
|
||||||
|
if choice.isdigit() and 1 <= int(choice) <= len(files):
|
||||||
|
return [files[int(choice) - 1]]
|
||||||
|
elif choice.lower() == 'all':
|
||||||
|
return files
|
||||||
|
else:
|
||||||
|
print_error("Invalid input. Please enter a valid number or 'all'.")
|
||||||
|
print()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def import_custom_formats(source_config, import_path='./custom_formats', selected_files=None, sync_mode=False):
|
||||||
headers = {"X-Api-Key": source_config['api_key']}
|
headers = {"X-Api-Key": source_config['api_key']}
|
||||||
get_url = f"{source_config['base_url']}/api/v3/customformat"
|
get_url = f"{source_config['base_url']}/api/v3/customformat"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(get_url, headers=headers)
|
response = requests.get(get_url, headers=headers)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
existing_formats = response.json()
|
existing_formats = response.json()
|
||||||
existing_names_to_id = {format['name']: format['id'] for format in existing_formats}
|
existing_names_to_id = {format['name']: format['id'] for format in existing_formats}
|
||||||
|
|
||||||
files = os.listdir(import_path)
|
if selected_files is None:
|
||||||
if auto_select_file and len(files) == 1:
|
selected_files = select_file(import_path, source_config['app_name'], sync_mode=sync_mode)
|
||||||
selected_file = files[0]
|
if not selected_files:
|
||||||
else:
|
return # Exit if no file is selected
|
||||||
selected_file = select_file(import_path)
|
|
||||||
|
|
||||||
added_count, updated_count = 0, 0
|
for selected_file in selected_files:
|
||||||
|
added_count, updated_count = 0, 0
|
||||||
|
with open(os.path.join(import_path, selected_file), 'r') as import_file:
|
||||||
|
import_formats = json.load(import_file)
|
||||||
|
|
||||||
with open(os.path.join(import_path, selected_file), 'r') as import_file:
|
for format in import_formats:
|
||||||
import_formats = json.load(import_file)
|
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())
|
||||||
|
|
||||||
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:
|
else:
|
||||||
print_error(f"Updating custom format '{format_name}': FAIL")
|
post_url = f"{source_config['base_url']}/api/v3/customformat"
|
||||||
print(response.content.decode())
|
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())
|
||||||
|
|
||||||
else:
|
print()
|
||||||
post_url = f"{source_config['base_url']}/api/v3/customformat"
|
print_success(f"Successfully added {added_count} custom formats, updated {updated_count} custom formats.")
|
||||||
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:
|
else:
|
||||||
print_error(f"Failed to retrieve existing custom formats from {get_url}! (HTTP {response.status_code})")
|
print_error(f"Failed to retrieve existing custom formats from {get_url}! (HTTP {response.status_code})")
|
||||||
@@ -113,35 +163,24 @@ def import_custom_formats(source_config, import_path='./custom_formats', auto_se
|
|||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
print_connection_error()
|
print_connection_error()
|
||||||
|
|
||||||
def import_quality_profiles(source_config, import_path='./profiles'):
|
|
||||||
|
|
||||||
|
def import_quality_profiles(source_config, import_path='./profiles', selected_files=None, sync_mode=False):
|
||||||
headers = {"X-Api-Key": source_config['api_key']}
|
headers = {"X-Api-Key": source_config['api_key']}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cf_import_sync(source_config)
|
cf_import_sync(source_config)
|
||||||
|
|
||||||
profile_dir = import_path
|
if not selected_files:
|
||||||
profiles = [f for f in os.listdir(profile_dir) if f.endswith('.json')]
|
if sync_mode:
|
||||||
|
# Automatically select all profile files
|
||||||
|
selected_files = [f for f in os.listdir(import_path) if os.path.isfile(os.path.join(import_path, f))]
|
||||||
|
|
||||||
print()
|
if not selected_files:
|
||||||
print(Colors.HEADER + "Available Profiles:" + Colors.ENDC)
|
return # Exit if no file is selected
|
||||||
for i, profile in enumerate(profiles, 1):
|
|
||||||
print(f"{i}. {profile}")
|
|
||||||
print(f"{len(profiles) + 1}. Import all profiles")
|
|
||||||
|
|
||||||
print()
|
|
||||||
selection = input("Please enter the number of the profile you want to import (or enter " + str(len(profiles) + 1) + " to import all): ")
|
|
||||||
selected_files = []
|
|
||||||
try:
|
|
||||||
selection = int(selection)
|
|
||||||
if selection == len(profiles) + 1:
|
|
||||||
selected_files = profiles
|
|
||||||
else:
|
|
||||||
selected_files = [profiles[selection - 1]]
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
print_error("Invalid selection, please enter a valid number.")
|
|
||||||
return
|
|
||||||
|
|
||||||
for selected_file in selected_files:
|
for selected_file in selected_files:
|
||||||
with open(os.path.join(profile_dir, selected_file), 'r') as file:
|
with open(os.path.join(import_path, selected_file), 'r') as file:
|
||||||
try:
|
try:
|
||||||
quality_profiles = json.load(file)
|
quality_profiles = json.load(file)
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
@@ -187,6 +226,8 @@ def import_quality_profiles(source_config, import_path='./profiles'):
|
|||||||
print_connection_error()
|
print_connection_error()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def cf_import_sync(source_config):
|
def cf_import_sync(source_config):
|
||||||
headers = {"X-Api-Key": source_config['api_key']}
|
headers = {"X-Api-Key": source_config['api_key']}
|
||||||
custom_format_url = f"{source_config['base_url']}/api/v3/customformat"
|
custom_format_url = f"{source_config['base_url']}/api/v3/customformat"
|
||||||
@@ -209,11 +250,16 @@ def cf_import_sync(source_config):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
user_choice = get_user_choice()
|
selected_app, selected_instance = get_user_choice()
|
||||||
source_config = get_app_config(user_choice)
|
source_config = get_app_config(selected_app, selected_instance)
|
||||||
|
source_config['app_name'] = selected_app
|
||||||
import_choice = get_import_choice()
|
import_choice = get_import_choice()
|
||||||
|
|
||||||
if import_choice == "1":
|
if import_choice == "1":
|
||||||
import_custom_formats(source_config)
|
selected_files = select_file('./custom_formats', selected_app)
|
||||||
|
if selected_files:
|
||||||
|
import_custom_formats(source_config, './custom_formats', selected_files)
|
||||||
elif import_choice == "2":
|
elif import_choice == "2":
|
||||||
import_quality_profiles(source_config)
|
selected_files = select_file('./profiles', selected_app)
|
||||||
|
if selected_files:
|
||||||
|
import_quality_profiles(source_config, './profiles', selected_files)
|
||||||
@@ -90,29 +90,7 @@
|
|||||||
"modifier": "none"
|
"modifier": "none"
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"quality": {
|
|
||||||
"id": 2,
|
|
||||||
"name": "DVD",
|
|
||||||
"source": "dvd",
|
|
||||||
"resolution": 0,
|
|
||||||
"modifier": "none"
|
|
||||||
},
|
|
||||||
"items": [],
|
|
||||||
"allowed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"quality": {
|
|
||||||
"id": 23,
|
|
||||||
"name": "DVD-R",
|
|
||||||
"source": "dvd",
|
|
||||||
"resolution": 480,
|
|
||||||
"modifier": "remux"
|
|
||||||
},
|
|
||||||
"items": [],
|
|
||||||
"allowed": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WEB 480p",
|
"name": "WEB 480p",
|
||||||
@@ -126,7 +104,7 @@
|
|||||||
"modifier": "none"
|
"modifier": "none"
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -137,12 +115,34 @@
|
|||||||
"modifier": "none"
|
"modifier": "none"
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"allowed": false,
|
"allowed": true,
|
||||||
"id": 1000
|
"id": 1000
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "DVD",
|
||||||
|
"source": "dvd",
|
||||||
|
"resolution": 0,
|
||||||
|
"modifier": "none"
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 23,
|
||||||
|
"name": "DVD-R",
|
||||||
|
"source": "dvd",
|
||||||
|
"resolution": 480,
|
||||||
|
"modifier": "remux"
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
"id": 20,
|
"id": 20,
|
||||||
|
|||||||
@@ -90,29 +90,7 @@
|
|||||||
"modifier": "none"
|
"modifier": "none"
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
|
||||||
{
|
|
||||||
"quality": {
|
|
||||||
"id": 2,
|
|
||||||
"name": "DVD",
|
|
||||||
"source": "dvd",
|
|
||||||
"resolution": 0,
|
|
||||||
"modifier": "none"
|
|
||||||
},
|
|
||||||
"items": [],
|
|
||||||
"allowed": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"quality": {
|
|
||||||
"id": 23,
|
|
||||||
"name": "DVD-R",
|
|
||||||
"source": "dvd",
|
|
||||||
"resolution": 480,
|
|
||||||
"modifier": "remux"
|
|
||||||
},
|
|
||||||
"items": [],
|
|
||||||
"allowed": false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WEB 480p",
|
"name": "WEB 480p",
|
||||||
@@ -126,7 +104,7 @@
|
|||||||
"modifier": "none"
|
"modifier": "none"
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -137,12 +115,34 @@
|
|||||||
"modifier": "none"
|
"modifier": "none"
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"allowed": false,
|
"allowed": true,
|
||||||
"id": 1000
|
"id": 1000
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "DVD",
|
||||||
|
"source": "dvd",
|
||||||
|
"resolution": 0,
|
||||||
|
"modifier": "none"
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 23,
|
||||||
|
"name": "DVD-R",
|
||||||
|
"source": "dvd",
|
||||||
|
"resolution": 480,
|
||||||
|
"modifier": "remux"
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
"id": 20,
|
"id": 20,
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
"resolution": 480
|
"resolution": 480
|
||||||
},
|
},
|
||||||
"items": [],
|
"items": [],
|
||||||
"allowed": false
|
"allowed": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"quality": {
|
"quality": {
|
||||||
|
|||||||
1023
profiles/2160p Optimal (Single Grab) (Radarr).json
Normal file
1023
profiles/2160p Optimal (Single Grab) (Radarr).json
Normal file
File diff suppressed because it is too large
Load Diff
919
profiles/2160p Optimal (Single Grab) (Sonarr).json
Normal file
919
profiles/2160p Optimal (Single Grab) (Sonarr).json
Normal file
@@ -0,0 +1,919 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "2160p Optimal (Single Grab)",
|
||||||
|
"upgradeAllowed": true,
|
||||||
|
"cutoff": 21,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 0,
|
||||||
|
"name": "Unknown",
|
||||||
|
"source": "unknown",
|
||||||
|
"resolution": 0
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 13,
|
||||||
|
"name": "Bluray-480p",
|
||||||
|
"source": "bluray",
|
||||||
|
"resolution": 480
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 4,
|
||||||
|
"name": "HDTV-720p",
|
||||||
|
"source": "television",
|
||||||
|
"resolution": 720
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 14,
|
||||||
|
"name": "WEBRip-720p",
|
||||||
|
"source": "webRip",
|
||||||
|
"resolution": 720
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 5,
|
||||||
|
"name": "WEBDL-720p",
|
||||||
|
"source": "web",
|
||||||
|
"resolution": 720
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 6,
|
||||||
|
"name": "Bluray-720p",
|
||||||
|
"source": "bluray",
|
||||||
|
"resolution": 720
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fallback SD",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "SDTV",
|
||||||
|
"source": "television",
|
||||||
|
"resolution": 480
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 12,
|
||||||
|
"name": "WEBRip-480p",
|
||||||
|
"source": "webRip",
|
||||||
|
"resolution": 480
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 8,
|
||||||
|
"name": "WEBDL-480p",
|
||||||
|
"source": "web",
|
||||||
|
"resolution": 480
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 2,
|
||||||
|
"name": "DVD",
|
||||||
|
"source": "dvd",
|
||||||
|
"resolution": 480
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"allowed": false,
|
||||||
|
"id": 1003
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fallback 1080p",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 10,
|
||||||
|
"name": "Raw-HD",
|
||||||
|
"source": "televisionRaw",
|
||||||
|
"resolution": 1080
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 9,
|
||||||
|
"name": "HDTV-1080p",
|
||||||
|
"source": "television",
|
||||||
|
"resolution": 1080
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"allowed": false,
|
||||||
|
"id": 1002
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Transparent Capable",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 3,
|
||||||
|
"name": "WEBDL-1080p",
|
||||||
|
"source": "web",
|
||||||
|
"resolution": 1080
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 15,
|
||||||
|
"name": "WEBRip-1080p",
|
||||||
|
"source": "webRip",
|
||||||
|
"resolution": 1080
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 7,
|
||||||
|
"name": "Bluray-1080p",
|
||||||
|
"source": "bluray",
|
||||||
|
"resolution": 1080
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"allowed": false,
|
||||||
|
"id": 1001
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 20,
|
||||||
|
"name": "Bluray-1080p Remux",
|
||||||
|
"source": "blurayRaw",
|
||||||
|
"resolution": 1080
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 16,
|
||||||
|
"name": "HDTV-2160p",
|
||||||
|
"source": "television",
|
||||||
|
"resolution": 2160
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 17,
|
||||||
|
"name": "WEBRip-2160p",
|
||||||
|
"source": "webRip",
|
||||||
|
"resolution": 2160
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 19,
|
||||||
|
"name": "Bluray-2160p",
|
||||||
|
"source": "bluray",
|
||||||
|
"resolution": 2160
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 18,
|
||||||
|
"name": "WEBDL-2160p",
|
||||||
|
"source": "web",
|
||||||
|
"resolution": 2160
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"quality": {
|
||||||
|
"id": 21,
|
||||||
|
"name": "Bluray-2160p Remux",
|
||||||
|
"source": "blurayRaw",
|
||||||
|
"resolution": 2160
|
||||||
|
},
|
||||||
|
"items": [],
|
||||||
|
"allowed": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"minFormatScore": 0,
|
||||||
|
"cutoffFormatScore": 0,
|
||||||
|
"formatItems": [
|
||||||
|
{
|
||||||
|
"format": 226,
|
||||||
|
"name": "PCM",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 225,
|
||||||
|
"name": "Blu-Ray (Remux)",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 224,
|
||||||
|
"name": "h265",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 223,
|
||||||
|
"name": "LiNG",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 214,
|
||||||
|
"name": "LQ",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 213,
|
||||||
|
"name": "EPSiLON",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 212,
|
||||||
|
"name": "playBD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 211,
|
||||||
|
"name": "BLURANiUM",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 210,
|
||||||
|
"name": "PmP",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 209,
|
||||||
|
"name": "WiLDCAT",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 208,
|
||||||
|
"name": "TRiToN",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 207,
|
||||||
|
"name": "3L",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 206,
|
||||||
|
"name": "Dolby Vision w/out Fallback",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 205,
|
||||||
|
"name": "UHDBits",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 204,
|
||||||
|
"name": "ATMOS (Missing)",
|
||||||
|
"score": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 203,
|
||||||
|
"name": "TrueHD (Missing)",
|
||||||
|
"score": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 202,
|
||||||
|
"name": "HDR10 (Missing)",
|
||||||
|
"score": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 201,
|
||||||
|
"name": "HDR10",
|
||||||
|
"score": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 200,
|
||||||
|
"name": "Dolby Vision",
|
||||||
|
"score": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 199,
|
||||||
|
"name": "HDR10+",
|
||||||
|
"score": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 198,
|
||||||
|
"name": "Stream Optimised",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 197,
|
||||||
|
"name": "LoRD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 196,
|
||||||
|
"name": "Scene",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 195,
|
||||||
|
"name": "mHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 194,
|
||||||
|
"name": "AV-1",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 193,
|
||||||
|
"name": "VVC",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 192,
|
||||||
|
"name": "Extras",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 191,
|
||||||
|
"name": "480p",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 190,
|
||||||
|
"name": "Dariush ",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 189,
|
||||||
|
"name": "TBB SD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 188,
|
||||||
|
"name": "Xvid",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 187,
|
||||||
|
"name": "DVD",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 186,
|
||||||
|
"name": "DVD REMUX ",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 185,
|
||||||
|
"name": "HANDJOB SD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 184,
|
||||||
|
"name": "iTunes (Missing)",
|
||||||
|
"score": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 183,
|
||||||
|
"name": "ROKU",
|
||||||
|
"score": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 182,
|
||||||
|
"name": "BeyondHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 181,
|
||||||
|
"name": "Disc ",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 180,
|
||||||
|
"name": "3D",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 179,
|
||||||
|
"name": "Unwanted",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 178,
|
||||||
|
"name": "RightSIZE",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 177,
|
||||||
|
"name": "Black and White",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 176,
|
||||||
|
"name": "TrueHD",
|
||||||
|
"score": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 175,
|
||||||
|
"name": "DTS-X",
|
||||||
|
"score": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 174,
|
||||||
|
"name": "FLAC",
|
||||||
|
"score": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 173,
|
||||||
|
"name": "DTS-HD MA",
|
||||||
|
"score": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 172,
|
||||||
|
"name": "x265",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 171,
|
||||||
|
"name": "IMAX",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 170,
|
||||||
|
"name": "ATMOS",
|
||||||
|
"score": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 169,
|
||||||
|
"name": "DD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 168,
|
||||||
|
"name": "DTS",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 167,
|
||||||
|
"name": "DD+",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 166,
|
||||||
|
"name": "iTunes",
|
||||||
|
"score": 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 165,
|
||||||
|
"name": "Paramount+",
|
||||||
|
"score": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 164,
|
||||||
|
"name": "Peacock TV",
|
||||||
|
"score": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 163,
|
||||||
|
"name": "Hulu",
|
||||||
|
"score": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 162,
|
||||||
|
"name": "Netflix",
|
||||||
|
"score": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 161,
|
||||||
|
"name": "HBO Max",
|
||||||
|
"score": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 160,
|
||||||
|
"name": "Disney+",
|
||||||
|
"score": 30
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 159,
|
||||||
|
"name": "Apple TV+",
|
||||||
|
"score": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 158,
|
||||||
|
"name": "Movies Anywhere",
|
||||||
|
"score": 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 157,
|
||||||
|
"name": "Amazon Prime",
|
||||||
|
"score": 40
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 156,
|
||||||
|
"name": "REMUX",
|
||||||
|
"score": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 155,
|
||||||
|
"name": "x264",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 154,
|
||||||
|
"name": "WEBRip",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 153,
|
||||||
|
"name": "Blu-Ray",
|
||||||
|
"score": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 152,
|
||||||
|
"name": "2160p",
|
||||||
|
"score": 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 151,
|
||||||
|
"name": "720p",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 150,
|
||||||
|
"name": "1080p",
|
||||||
|
"score": -9999
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 149,
|
||||||
|
"name": "BHDStudio",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 148,
|
||||||
|
"name": "LEGi0N",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 147,
|
||||||
|
"name": "FraMeSToR",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 146,
|
||||||
|
"name": "iFT",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 145,
|
||||||
|
"name": "MTeam",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 144,
|
||||||
|
"name": "EDPH",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 143,
|
||||||
|
"name": "HANDJOB",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 142,
|
||||||
|
"name": "c0ke",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 141,
|
||||||
|
"name": "KASHMiR",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 140,
|
||||||
|
"name": "WMING",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 139,
|
||||||
|
"name": "playHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 138,
|
||||||
|
"name": "E.N.D",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 137,
|
||||||
|
"name": "GutS",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 136,
|
||||||
|
"name": "BV",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 135,
|
||||||
|
"name": "SiMPLE",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 134,
|
||||||
|
"name": "W4NK3R",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 133,
|
||||||
|
"name": "GS88",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 132,
|
||||||
|
"name": "HiP",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 131,
|
||||||
|
"name": "GALAXY",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 130,
|
||||||
|
"name": "luvBB",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 129,
|
||||||
|
"name": "NyHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 128,
|
||||||
|
"name": "ZIMBO",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 127,
|
||||||
|
"name": "ThD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 126,
|
||||||
|
"name": "SaNcTi",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 125,
|
||||||
|
"name": "ORiGEN",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 124,
|
||||||
|
"name": "Positive",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 123,
|
||||||
|
"name": "ESiR",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 122,
|
||||||
|
"name": "Chotab",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 121,
|
||||||
|
"name": "xander",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 120,
|
||||||
|
"name": "FTW-HD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 119,
|
||||||
|
"name": "Penumbra",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 118,
|
||||||
|
"name": "TDD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 117,
|
||||||
|
"name": "Dariush SD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 116,
|
||||||
|
"name": "PTer",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 115,
|
||||||
|
"name": "SbR",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 114,
|
||||||
|
"name": "nmd",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 113,
|
||||||
|
"name": "TBB",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 112,
|
||||||
|
"name": "EA",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 111,
|
||||||
|
"name": "BMF",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 110,
|
||||||
|
"name": "LolHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 109,
|
||||||
|
"name": "HDMaNiAcS",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 108,
|
||||||
|
"name": "IDE",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 107,
|
||||||
|
"name": "de[42]",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 106,
|
||||||
|
"name": "NCmt",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 105,
|
||||||
|
"name": "decibeL",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 104,
|
||||||
|
"name": "NTb",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 103,
|
||||||
|
"name": "CRiSC",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 102,
|
||||||
|
"name": "SA89",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 101,
|
||||||
|
"name": "HiDt",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 100,
|
||||||
|
"name": "FoRM",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 99,
|
||||||
|
"name": "HiFi",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 98,
|
||||||
|
"name": "CtrlHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 97,
|
||||||
|
"name": "VietHD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 96,
|
||||||
|
"name": "ZQ",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 95,
|
||||||
|
"name": "TayTo",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 94,
|
||||||
|
"name": "Geek",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 93,
|
||||||
|
"name": "EbP",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 92,
|
||||||
|
"name": "DON",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 91,
|
||||||
|
"name": "D-Z0N3",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 215,
|
||||||
|
"name": "CJ",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 216,
|
||||||
|
"name": "pcroland",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 217,
|
||||||
|
"name": "BTN",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 218,
|
||||||
|
"name": "iON",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 219,
|
||||||
|
"name": "AJP69",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 220,
|
||||||
|
"name": "VLAD",
|
||||||
|
"score": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": 221,
|
||||||
|
"name": "hdalx",
|
||||||
|
"score": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
40
syncarr.py
40
syncarr.py
@@ -1,33 +1,40 @@
|
|||||||
import exportarr
|
import yaml
|
||||||
import importarr
|
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
import exportarr # Assuming this module contains the export functions
|
||||||
|
import importarr # Assuming this module contains the import functions
|
||||||
|
|
||||||
def sync_data():
|
def sync_data(sync_mode=False):
|
||||||
# Load configuration for main app
|
# Load configuration from YAML file
|
||||||
with open('config.json', 'r') as config_file:
|
with open('config.yml', 'r') as config_file:
|
||||||
config = json.load(config_file)
|
config = yaml.safe_load(config_file)
|
||||||
|
|
||||||
# Specify the temporary path where files were saved
|
# Specify the temporary path where files will be saved
|
||||||
temp_cf_path = './temp_directory/custom_formats'
|
temp_cf_path = './temp_directory/custom_formats'
|
||||||
temp_qf_path = './temp_directory/quality_profiles'
|
temp_qf_path = './temp_directory/quality_profiles'
|
||||||
|
|
||||||
# Get user choice for app (radarr/sonarr)
|
# Get user choice for app (radarr/sonarr)
|
||||||
app_choice = importarr.get_user_choice()
|
app_choice = input("Select the app you want to sync:\n1. Radarr\n2. Sonarr\nEnter your choice (1 or 2): ").strip()
|
||||||
|
|
||||||
# Export data for the chosen app
|
while app_choice not in ["1", "2"]:
|
||||||
exportarr.export_cf(app_choice, save_path=temp_cf_path)
|
print("Invalid input. Please enter 1 for Radarr or 2 for Sonarr.")
|
||||||
exportarr.export_qf(app_choice, save_path=temp_qf_path)
|
app_choice = input("Enter your choice (1 or 2): ").strip()
|
||||||
|
|
||||||
|
app_choice = "radarr" if app_choice == "1" else "sonarr"
|
||||||
|
|
||||||
|
instance_name = "temp"
|
||||||
|
exportarr.export_cf(app_choice, instance_name, save_path=temp_cf_path)
|
||||||
|
exportarr.export_qf(app_choice, instance_name, save_path=temp_qf_path)
|
||||||
|
|
||||||
# Sync with each extra installation of the chosen app
|
# Sync with each extra installation of the chosen app
|
||||||
for extra_instance in config['extra_installations'].get(app_choice, []):
|
for extra_instance in config['instances'].get('extras', {}).get(app_choice, []):
|
||||||
source_config = extra_instance
|
|
||||||
print(f"Importing to instance: {extra_instance['name']}")
|
print(f"Importing to instance: {extra_instance['name']}")
|
||||||
|
|
||||||
# Import custom formats and quality profiles to each extra instance
|
# Import custom formats and quality profiles to each extra instance
|
||||||
importarr.import_custom_formats(source_config, import_path=temp_cf_path, auto_select_file=True)
|
extra_instance['app_name'] = app_choice
|
||||||
importarr.import_quality_profiles(source_config, import_path=temp_qf_path)
|
importarr.import_custom_formats(extra_instance, import_path=temp_cf_path, selected_files=None, sync_mode=sync_mode)
|
||||||
|
importarr.import_quality_profiles(extra_instance, import_path=temp_qf_path, selected_files=None, sync_mode=sync_mode)
|
||||||
|
|
||||||
# Delete the temporary directories after the sync is complete
|
# Delete the temporary directories after the sync is complete
|
||||||
temp_directory = './temp_directory'
|
temp_directory = './temp_directory'
|
||||||
@@ -36,4 +43,5 @@ def sync_data():
|
|||||||
print(f"Deleted temporary directory: {temp_directory}")
|
print(f"Deleted temporary directory: {temp_directory}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sync_data()
|
# Set sync_mode to True to enable automatic selection of all files during import
|
||||||
|
sync_data(sync_mode=True)
|
||||||
Reference in New Issue
Block a user