Files
database/scripts/tierCreator.py
santiagosayshey 5682c46233 New Profile: 2160p Quality (#19)
create(script): Add tierCreator script for generating group tier custom formats + release group regex patterns

fix(tierCreator): Update YAML handling and improve template processing for group tiers

remove(template): release group condition placeholder

create(format): 2160p Quality Tiers
- 69 new release group regex patterns
- 6 new tiers for 2160p Quality

create(data): Add 2160p Quality Tiers JSON file with detailed statistics and scoring

docs(README): Update README to include utility scripts and tier creator usage instructions

fix(tierCreator): Improve output message to indicate whether a file is being created or overwritten

feat(tierCreator): Add show_preview option for dry run mode to display regex patterns and custom formats

add(tier): more data for 2160p Quality

add(tiers): new regex patterns for various groups, tweaks to 2160p quality tiers

tweak(format): add WOU and MALUS to unwanted x265

tweak(format): Seperate streaming services into 1080p / 2160p
- Existing ones still exist, there are just needed for the 2160p Quality profile since it needs different scores based on the resolution

create(format): Standard Dynamic Range

create(format): Match x265 only for 1080p
- Used to get rid of 1080p x265 encodes for 2160p Quality (since x265 is needed for 2160p Encodes)

tweak(regex): DTS & X can now be seperated by a colon

tweak(format): Add STRiKE to missing HDR Groups

create(profile): Initialise 2160p Quality

tweak(tier): adjustments to scoring algorithm
- hard limits on efficiency delta / num releases for tier 3+

tweaks(tier): new groups / tier adjustments for 2160p quality

add(format): x264 (1080p) 
- Match x264 2160p encodes

tweak(profile): Various improvements
- Remove UHD Bluray scores, they bloat the scoring logic
- Reduce SDR WEBs to below tier ~4
- Add some notes on scoring logic to description

fix(tier): Remove bad data

fix(tier): remove groups added via bad data

tweak(tier): target 55% efficiency, new data for SA89, Mainframe

add(profile): x265 (Missing 2160p)
- Append x265 to 2160 Blu-ray encodes that are not explicitly labelled x264

tweak(profile): Various improvements / tweaks
- Remove required groups for HDR missing CF
- New CF for unknown lossless audio
- New 2160p Balanced tiers
- Normalise lossless audio / quality tier scores

tweak(profile): Adjust fallbacks / improve descriptions 
- Removed HDTV for 1080p profiles
- Renamed 'fallbacks' to 'SD'

tweak(profile): Finalise 2160p Quality v1
- Set upgrade until to 2160p Transparent, score = 800, min score increment = 5
- Add comprehensive description
2025-01-18 17:27:44 +10:30

211 lines
6.9 KiB
Python

#!/usr/bin/env python3
import argparse
import json
import os
import sys
from pathlib import Path
import yaml
def load_template(template_path):
"""Load a YAML template file."""
try:
with open(template_path, 'r') as f:
return yaml.safe_load(f)
except FileNotFoundError:
print(f"Error: Template file not found: {template_path}")
sys.exit(1)
def create_regex_pattern(group_name,
template_dir,
output_dir,
dry_run=False,
show_preview=False):
"""Create a regex pattern file for a release group if it doesn't exist."""
output_path = output_dir / f"{group_name}.yml"
# Skip if pattern already exists
if output_path.exists():
print(f"Skipping existing regex pattern: {output_path}")
return
print(
f"{'Would create' if dry_run else 'Creating'} regex pattern: {output_path}"
)
# Load and fill template
template = load_template(template_dir / "releaseGroup.yml")
template['name'] = group_name
template['pattern'] = f"(?<=^|[\\s.-]){group_name}\\b"
# Show preview in dry run mode if this is the first pattern
if dry_run and show_preview:
print("\nPreview of first regex pattern:")
print("---")
print(
yaml.dump(template,
sort_keys=False,
default_flow_style=False,
indent=2))
print("---\n")
# Create output directory if it doesn't exist
output_dir.mkdir(parents=True, exist_ok=True)
# Write pattern file if not dry run
if not dry_run:
with open(output_path, 'w') as f:
yaml.dump(template, f, sort_keys=False)
def create_tier_format(tier,
resolution,
type_name,
groups,
template_dir,
output_dir,
dry_run=False,
show_preview=False):
"""Create a custom format file for a specific tier."""
# Get groups for this tier
tier_groups = [group["name"] for group in groups if group["tier"] == tier]
if not tier_groups:
return
# Load and fill template
template = load_template(template_dir / "groupTier.yml")
# Replace template variables
template['name'] = f"{resolution} {type_name} Tier {tier}"
template[
'description'] = f"Matches release groups who fall under {resolution} {type_name} Tier {tier}"
# Find and update resolution condition
for condition in template['conditions']:
if condition.get('resolution'):
condition['name'] = resolution
condition['resolution'] = resolution
# Add release group conditions
for group_name in tier_groups:
release_group_condition = {
'name': group_name,
'negate': False,
'pattern': group_name,
'required': False,
'type': 'release_group'
}
template['conditions'].append(release_group_condition)
# Ensure tests is an empty list, not null
template['tests'] = []
# Create output directory if it doesn't exist
output_dir.mkdir(parents=True, exist_ok=True)
# Write custom format file
output_path = output_dir / f"{resolution} {type_name} Tier {tier}.yml"
existing = "Overwriting" if output_path.exists() else "Creating"
print(
f"{'Would ' + existing.lower() if dry_run else existing} custom format: {output_path} (includes {len(tier_groups)} groups)"
)
# Show preview in dry run mode if this is the first format
if dry_run and show_preview:
print("\nPreview of first custom format:")
print("---")
print(
yaml.dump(template,
sort_keys=False,
default_flow_style=False,
indent=2))
print("---\n")
if not dry_run:
with open(output_path, 'w') as f:
yaml.dump(template,
f,
sort_keys=False,
default_flow_style=False,
indent=2)
def main():
parser = argparse.ArgumentParser(
description='Create Radarr custom formats for release group tiers')
parser.add_argument('json_file',
help='Input JSON file containing tier data')
parser.add_argument('--resolution',
choices=['SD', '720p', '1080p', '2160p'],
required=True,
help='Resolution for custom formats')
parser.add_argument('--type',
choices=['Quality', 'Balanced'],
required=True,
help='Type of custom format')
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making any changes')
args = parser.parse_args()
# Setup paths
script_dir = Path(__file__).parent
template_dir = script_dir.parent / "templates"
regex_dir = script_dir.parent / "regex_patterns"
format_dir = script_dir.parent / "custom_formats"
# Load and parse input JSON
try:
with open(args.json_file, 'r') as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: Input JSON file not found: {args.json_file}")
sys.exit(1)
except json.JSONDecodeError:
print(f"Error: Invalid JSON file: {args.json_file}")
sys.exit(1)
# Print summary of what we found
print(f"\nAnalyzing input file: {args.json_file}")
print(
f"Found {len(data['tiered_groups'])} release groups across {len(set(group['tier'] for group in data['tiered_groups']))} tiers"
)
if args.dry_run:
print("\nDRY RUN - No files will be created or modified\n")
# Create regex patterns for all groups
print("\nProcessing regex patterns:")
for i, group in enumerate(data["tiered_groups"]):
create_regex_pattern(group["name"],
template_dir,
regex_dir,
args.dry_run,
show_preview=(i == 0))
# Create tier formats
print("\nProcessing custom formats:")
unique_tiers = sorted(set(group["tier"]
for group in data["tiered_groups"]))
for i, tier in enumerate(unique_tiers):
create_tier_format(tier,
args.resolution,
args.type,
data["tiered_groups"],
template_dir,
format_dir,
args.dry_run,
show_preview=(i == 0))
print(
f"\nSuccessfully {'simulated' if args.dry_run else 'created'} custom formats for {args.resolution} {args.type}"
)
if __name__ == "__main__":
main()