Files
profilarr/backend/app/compile/mappings.py
santiagosayshey d8f944af11 improvements: whole lotta stuff (#18)
* feat: initialise task scheduler

* feat: add "next run" field to task status

* fix: adjust status route path

* feat: task dashboard + api functions

* fix: return sucess object for alert

* refactor: turn task cards into seperate objects

* fix: change task names

* feat: implement compile and import module
- only working for custom formats for now

* refactor/feature: refactor compilation module
- seperate file for mappings
- seperate format compiler into new file
- add compiler for profiles

* feat: add import logic for quality profiles

* fix: properly resolve cutoff IDs for singular qualities

* fix: remux mappings for sonarr

* fix: retain quality group order
- stop groups first, then singular

* fix: dynamically find next group ID to stop duplicate IDs from occuring

* fix: normalise quality letter case

* feat: add api functions for import functionality

* fix: adjust validation for import.js

* feat: add mass selection tool componnet
- keyboard shortcuts to enter state
- mass delete / import

* feat: add loading indicator on import

* style: improve selected card styling

* fix: append extra custom formats with 0 score

* perf: add git status caching to improve load times

* fix: adjust mass import handling and selection logic
- use content.name rather than content.id

* fix: enhance quality name mapping with alternate names and case-insensitive lookups

* feat: add description truncation to ProfileCard for improved readability
- also remove qualitites

* fix: update upgrade quality selection logic to handle disabling scenarios

* feat: expand language mappings with additional languages and identifiers

* feat: enhance profile conversion logging with language handling

* fix: clarify language setting impact in ProfileLanguagesTab component

* feat: add Sonarr language mappings and update language selection logic

* feat: implement language normalization and enhance logging in format conversion

* feat: enhance logging in format conversion and add language specification handling

* feat: add Afrikaans and Albanian languages to the language constants

* refactor: remove language strictness feature and update language handling in ProfileModal

* feat: add logging setup for improved debugging in mappings module

* feat: enhance language compilation to work with new system

* feat: update language handling in ProfileLanguagesTab to support 'any' behavior

* fix: remove redundant import statement in profile.py

* feat: implement in-memory format import functionality and update profile compiler to utilize it for non english language compilation

* feat: add comparison for tweaks in quality profile changes

* feat: add functions to convert display names to filenames and vice versa

* fix: remove unnecessary filename modification in formats data

* feat: add process_tweaks function to handle profile tweaks and import formats before compilation

* feat: refactor process_tweaks to modularize format import and scoring
- include lossless audio tweak handling

* feat: add support for Dolby Vision no fallback and bleeding edge codecs in process_tweaks

* feat: add support for disabling prereleases based on profile tweaks

* fix: reduce cache TTL from 30 seconds to 1 second for quicker updates

* feat: add resetState function to initialize profile modal state

* feat: enhance save_yaml_file function to optionally use data name for filename

* feat: enhance handle_rename function to account for staged renames

* feat: enhance revert functionality to handle untracked files and staged deletions

* chore: update timezone setting in docker-compose.yml

* feat: increase maximum description length in ProfileCard component

* refactor: generic YAML comparison and change summary functionality

* refactor: incoming changes now uses generic comparison logic

* refactor: add YAML conflict comparison functionality with detailed summary generation for merge conflicts

* refactor: heavily simplified resolve conflict modal to work with new generic conflict parsing

* refactor: implement GitStatusManager for improved repository status handling and sync task updates

* refactor: integrate GitStatusManager to update remote status after pull operations

* refactor: remove isDevMode prop from ChangeRow, ConflictRow, ConflictTable, ChangeTable, RepoContainer, and ActionButtons components

* refactor: migrate settings handling from settings_utils to db module and enhance settings management

* refactor: remove deprecated authentication methods and streamline push operations with SSH access

* refactor: enhance error handling and authentication for Git operations, including PAT support

* refactor: remove old settings_utils

* refactor: add settings prop to StatusContainer and update database initialization for profilarr_pat

* refactor: simplify format name handling in ViewChanges component by removing old API call

* fix: update fetchSettings to handle cases with no git repository and ensure settings is null

* fix: enhance get_git_status to return a valid status object when no git repository is found

* style: various improvements to repo container
- add database stats
- add organisation / profile avatar
- parse organisation / repo name
- improvements to branch button

* feat: implement authentication setup and middleware for secure session management

* fix: auto login after setting up authentication

* feat: enhance authentication setup with GET method and track failed attempts

* feat: add authentication setup and login components with API integration

* feat: redesign SetupPage component with improved layout and user guidance

* feat: enhance LoginPage layout with improved design and user guidance

* refactor: remove unused API functions for regex and format management

* improvements: whole lot more stuff (#17)

* feat: implement configuration management for directory paths and session settings

* feat: implement backup management with API endpoints for backup operations

* feat: add backup import functionality with zip file validation and restoration

* feat: implement backup API with endpoints for listing, creating, downloading, restoring, deleting, and importing backups

* feat: enhance backup listing with file size and last modified time

* feat: add backup management interface with listing, creation, restoration, and deletion functionalities

* fix: refresh backups list after successful deletion

* feat: create BackupCard component for displaying backup details and actions

* fix: status parsing improvements
- now properly shows outgoing changes with / without developer mode

* fix: remove authentication bypass for backup routes during testing

* feat: add logging configuration and ensure log directory creation

* feat: implement application-wide logging configuration and ensure log directory creation

* feat: add logging blueprint with endpoints for retrieving and searching log files

* feat: add git logging configuration to enhance logging capabilities

* refactor: enhance logging details and improve error handling in repository cloning and file processing

* refactor: update tab labels for clarity in settings page

* refactor: remove unused tasks and streamline task scheduler

* refactor: improve repository settings handling and UI updates in RepoContainer

* style: add slight gradient to modal

* feat: enhance footer with GitHub repository info and organization avatar

* refactor: update imports and enhance modal layout for linking Git repository

* refactor: remove documentation and issue links from footer component

* refactor: git settings refactor.
- new git container to contain repo / status
- split repo container into active / empty components
- split status into seperate sections for incoming, outgoing, conflict

* chore: simplify environment configuration by using .env file in docker-compose

* fix: update remote status after commit and push operations

* refactor: restructure application initialization and configure Git user settings on startup

* refactor: improve default Git user configuration handling in initialization

* style: enhance UI styling and structure for Git status display

* style: enhance UI layout and styling for ChangeRow and ChangeTable components

* style: enhance UI layout and styling for CommitMessage component

* style: improve selected row styling

* style: enhance UI styling for ConflictRow and ConflictTable components

* style: update merge process to include remote status update after finalization

* style: update noChangesMessages for improved clarity and engagement

* feat: add animated Logo component and integrate it into Navbar

* feat: enhance selection handling
- add shift selection
- add will be selected state and styling

* feat: enhance mouse tracking for shift selection

* refactor: remove deprecated ActionButtons

* style: unify button colors and update tooltips in IncomingChanges and OutgoingChanges components

* feat: add auto-pull feature with toggle in settings and backend support

* fix: update auto-pull implementation to use integer values

* feat: implement auto-pull functionality in remote status update

* file: improve value formatting for new files

* fix: change logger level to DEBUG and add debug message for profile import attempts

* refactor: remove logging statements and streamline exception handling in format compiler

* feat: enhance format import process with detailed logging and error handling

* feat: enhance profile import process with detailed logging and error handling

* refactor: remove non error logging statements for profile compilation

* feat: enhance logging for memory-based format import with detailed success and error messages

* feat: add logging for language settings and compiled profile data in profile import

* feat: add dedicated logging for importarr with separate log file and configuration

* feat: add logging API with functions to fetch logs, search, and filter by level

* feat: add logs tab to settings page with LogContainer component

* feat: add LogContainer and LogViewer components for enhanced log management

* fix: dynamic vertical height for log viewer

* fix: reduce log file size and increase backup count for improved log management

* fix: enhance error logging with exception type and full traceback for better debugging

* fix: use mapped cutoff name for profile conversion to ensure correct quality mapping

* fix: add validation for git repository existence before syncing

* fix: implement delete constraints check before item deletion to prevent breaking references

* fix: enhance delete constraints check with improved logging and name normalization

* fix: add protection against deletion of required custom formats in delete constraints check

* feat: implement ANSI color parsing in LogViewer for improved log readability

* feat: enhance ViewChanges component with improved key parsing and rendering of changes

* refactor: improve styling and structure of ResolveConflicts component for better readability and usability

* fix: improve error handling and response for arr config saving

* feat: extend arr configuration with additional fields and sync methods

* feat: add DataSelectorModal component for selecting data to sync

* feat: enhance ArrModal and DataSelectorModal with improved layout and data display

* feat: update ArrContainer to use new API import and include additional arrConfig fields

* feat: enhance ArrCard component with sync details and improved layout

* feat: add AddButton component with custom positioning and animations

* feat: replace Add New Card section with AddButton component for improved UX

* feat: reposition AddButton in ArrContainer for better visibility

* feat: replace AddNewCard with AddButton in RegexPage for improved UX

* feat: replace AddNewCard with AddButton in FormatPage for improved UX

* feat: replace AddNewCard with AddButton in ProfilePage for improved UX
2025-02-05 16:09:59 +10:30

961 lines
23 KiB
Python

# app/compile/mappings.py
"""Centralized constants and mappings for arr applications"""
from enum import Enum, auto
from typing import Dict, Any
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
class TargetApp(Enum):
"""Enum for target application types"""
RADARR = auto()
SONARR = auto()
class IndexerFlags:
"""Indexer flag mappings for both applications"""
RADARR = {
'freeleech': 1,
'halfleech': 2,
'double_upload': 4,
'internal': 32,
'scene': 128,
'freeleech_75': 256,
'freeleech_25': 512,
'nuked': 2048,
'ptp_golden': 8,
'ptp_approved': 16
}
SONARR = {
'freeleech': 1,
'halfleech': 2,
'double_upload': 4,
'internal': 8,
'scene': 16,
'freeleech_75': 32,
'freeleech_25': 64,
'nuked': 128
}
class Sources:
"""Source mappings for both applications"""
RADARR = {
'cam': 1,
'telesync': 2,
'telecine': 3,
'workprint': 4,
'dvd': 5,
'tv': 6,
'web_dl': 7,
'webrip': 8,
'bluray': 9
}
SONARR = {
'television': 1,
'televisionraw': 2,
'web_dl': 3,
'webrip': 4,
'dvd': 5,
'bluray': 6,
'blurayraw': 7
}
class Qualities:
"""Quality mappings for both applications"""
COMMON_RESOLUTIONS = {
'360p': 360,
'480p': 480,
'540p': 540,
'576p': 576,
'720p': 720,
'1080p': 1080,
'2160p': 2160
}
RADARR = {
"Unknown": {
"id": 0,
"name": "Unknown",
"source": "unknown",
"resolution": 0
},
"SDTV": {
"id": 1,
"name": "SDTV",
"source": "tv",
"resolution": 480
},
"DVD": {
"id": 2,
"name": "DVD",
"source": "dvd",
"resolution": 480
},
"WEBDL-1080p": {
"id": 3,
"name": "WEBDL-1080p",
"source": "webdl",
"resolution": 1080
},
"HDTV-720p": {
"id": 4,
"name": "HDTV-720p",
"source": "tv",
"resolution": 720
},
"WEBDL-720p": {
"id": 5,
"name": "WEBDL-720p",
"source": "webdl",
"resolution": 720
},
"Bluray-720p": {
"id": 6,
"name": "Bluray-720p",
"source": "bluray",
"resolution": 720
},
"Bluray-1080p": {
"id": 7,
"name": "Bluray-1080p",
"source": "bluray",
"resolution": 1080
},
"WEBDL-480p": {
"id": 8,
"name": "WEBDL-480p",
"source": "webdl",
"resolution": 480
},
"HDTV-1080p": {
"id": 9,
"name": "HDTV-1080p",
"source": "tv",
"resolution": 1080
},
"Raw-HD": {
"id": 10,
"name": "Raw-HD",
"source": "tv",
"resolution": 1080
},
"WEBRip-480p": {
"id": 12,
"name": "WEBRip-480p",
"source": "webrip",
"resolution": 480
},
"WEBRip-720p": {
"id": 14,
"name": "WEBRip-720p",
"source": "webrip",
"resolution": 720
},
"WEBRip-1080p": {
"id": 15,
"name": "WEBRip-1080p",
"source": "webrip",
"resolution": 1080
},
"HDTV-2160p": {
"id": 16,
"name": "HDTV-2160p",
"source": "tv",
"resolution": 2160
},
"WEBRip-2160p": {
"id": 17,
"name": "WEBRip-2160p",
"source": "webrip",
"resolution": 2160
},
"WEBDL-2160p": {
"id": 18,
"name": "WEBDL-2160p",
"source": "webdl",
"resolution": 2160
},
"Bluray-2160p": {
"id": 19,
"name": "Bluray-2160p",
"source": "bluray",
"resolution": 2160
},
"Bluray-480p": {
"id": 20,
"name": "Bluray-480p",
"source": "bluray",
"resolution": 480
},
"Bluray-576p": {
"id": 21,
"name": "Bluray-576p",
"source": "bluray",
"resolution": 576
},
"BR-DISK": {
"id": 22,
"name": "BR-DISK",
"source": "bluray",
"resolution": 1080
},
"DVD-R": {
"id": 23,
"name": "DVD-R",
"source": "dvd",
"resolution": 480
},
"WORKPRINT": {
"id": 24,
"name": "WORKPRINT",
"source": "workprint",
"resolution": 0
},
"CAM": {
"id": 25,
"name": "CAM",
"source": "cam",
"resolution": 0
},
"TELESYNC": {
"id": 26,
"name": "TELESYNC",
"source": "telesync",
"resolution": 0
},
"TELECINE": {
"id": 27,
"name": "TELECINE",
"source": "telecine",
"resolution": 0
},
"DVDSCR": {
"id": 28,
"name": "DVDSCR",
"source": "dvd",
"resolution": 480
},
"REGIONAL": {
"id": 29,
"name": "REGIONAL",
"source": "dvd",
"resolution": 480
},
"Remux-1080p": {
"id": 30,
"name": "Remux-1080p",
"source": "bluray",
"resolution": 1080
},
"Remux-2160p": {
"id": 31,
"name": "Remux-2160p",
"source": "bluray",
"resolution": 2160
}
}
SONARR = {
"Unknown": {
"id": 0,
"name": "Unknown",
"source": "unknown",
"resolution": 0
},
"SDTV": {
"id": 1,
"name": "SDTV",
"source": "television",
"resolution": 480
},
"DVD": {
"id": 2,
"name": "DVD",
"source": "dvd",
"resolution": 480
},
"WEBDL-1080p": {
"id": 3,
"name": "WEBDL-1080p",
"source": "web",
"resolution": 1080
},
"HDTV-720p": {
"id": 4,
"name": "HDTV-720p",
"source": "television",
"resolution": 720
},
"WEBDL-720p": {
"id": 5,
"name": "WEBDL-720p",
"source": "web",
"resolution": 720
},
"Bluray-720p": {
"id": 6,
"name": "Bluray-720p",
"source": "bluray",
"resolution": 720
},
"Bluray-1080p": {
"id": 7,
"name": "Bluray-1080p",
"source": "bluray",
"resolution": 1080
},
"WEBDL-480p": {
"id": 8,
"name": "WEBDL-480p",
"source": "web",
"resolution": 480
},
"HDTV-1080p": {
"id": 9,
"name": "HDTV-1080p",
"source": "television",
"resolution": 1080
},
"Raw-HD": {
"id": 10,
"name": "Raw-HD",
"source": "televisionRaw",
"resolution": 1080
},
"WEBRip-480p": {
"id": 12,
"name": "WEBRip-480p",
"source": "webRip",
"resolution": 480
},
"Bluray-480p": {
"id": 13,
"name": "Bluray-480p",
"source": "bluray",
"resolution": 480
},
"WEBRip-720p": {
"id": 14,
"name": "WEBRip-720p",
"source": "webRip",
"resolution": 720
},
"WEBRip-1080p": {
"id": 15,
"name": "WEBRip-1080p",
"source": "webRip",
"resolution": 1080
},
"HDTV-2160p": {
"id": 16,
"name": "HDTV-2160p",
"source": "television",
"resolution": 2160
},
"WEBRip-2160p": {
"id": 17,
"name": "WEBRip-2160p",
"source": "webRip",
"resolution": 2160
},
"WEBDL-2160p": {
"id": 18,
"name": "WEBDL-2160p",
"source": "web",
"resolution": 2160
},
"Bluray-2160p": {
"id": 19,
"name": "Bluray-2160p",
"source": "bluray",
"resolution": 2160
},
"Bluray-1080p Remux": {
"id": 20,
"name": "Bluray-1080p Remux",
"source": "blurayRaw",
"resolution": 1080
},
"Bluray-2160p Remux": {
"id": 21,
"name": "Bluray-2160p Remux",
"source": "blurayRaw",
"resolution": 2160
},
"Bluray-576p": {
"id": 22,
"name": "Bluray-576p",
"source": "bluray",
"resolution": 576
}
}
class Languages:
"""Language mappings for both applications"""
RADARR = {
'any': {
'id': -1,
'name': 'Any'
},
'original': {
'id': -2,
'name': 'Original'
},
'unknown': {
'id': 0,
'name': 'Unknown'
},
'english': {
'id': 1,
'name': 'English'
},
'french': {
'id': 2,
'name': 'French'
},
'spanish': {
'id': 3,
'name': 'Spanish'
},
'german': {
'id': 4,
'name': 'German'
},
'italian': {
'id': 5,
'name': 'Italian'
},
'danish': {
'id': 6,
'name': 'Danish'
},
'dutch': {
'id': 7,
'name': 'Dutch'
},
'japanese': {
'id': 8,
'name': 'Japanese'
},
'icelandic': {
'id': 9,
'name': 'Icelandic'
},
'chinese': {
'id': 10,
'name': 'Chinese'
},
'russian': {
'id': 11,
'name': 'Russian'
},
'polish': {
'id': 12,
'name': 'Polish'
},
'vietnamese': {
'id': 13,
'name': 'Vietnamese'
},
'swedish': {
'id': 14,
'name': 'Swedish'
},
'norwegian': {
'id': 15,
'name': 'Norwegian'
},
'finnish': {
'id': 16,
'name': 'Finnish'
},
'turkish': {
'id': 17,
'name': 'Turkish'
},
'portuguese': {
'id': 18,
'name': 'Portuguese'
},
'flemish': {
'id': 19,
'name': 'Flemish'
},
'greek': {
'id': 20,
'name': 'Greek'
},
'korean': {
'id': 21,
'name': 'Korean'
},
'hungarian': {
'id': 22,
'name': 'Hungarian'
},
'hebrew': {
'id': 23,
'name': 'Hebrew'
},
'lithuanian': {
'id': 24,
'name': 'Lithuanian'
},
'czech': {
'id': 25,
'name': 'Czech'
},
'hindi': {
'id': 26,
'name': 'Hindi'
},
'romanian': {
'id': 27,
'name': 'Romanian'
},
'thai': {
'id': 28,
'name': 'Thai'
},
'bulgarian': {
'id': 29,
'name': 'Bulgarian'
},
'portuguese_br': {
'id': 30,
'name': 'Portuguese (Brazil)'
},
'arabic': {
'id': 31,
'name': 'Arabic'
},
'ukrainian': {
'id': 32,
'name': 'Ukrainian'
},
'persian': {
'id': 33,
'name': 'Persian'
},
'bengali': {
'id': 34,
'name': 'Bengali'
},
'slovak': {
'id': 35,
'name': 'Slovak'
},
'latvian': {
'id': 36,
'name': 'Latvian'
},
'spanish_latino': {
'id': 37,
'name': 'Spanish (Latino)'
},
'catalan': {
'id': 38,
'name': 'Catalan'
},
'croatian': {
'id': 39,
'name': 'Croatian'
},
'serbian': {
'id': 40,
'name': 'Serbian'
},
'bosnian': {
'id': 41,
'name': 'Bosnian'
},
'estonian': {
'id': 42,
'name': 'Estonian'
},
'tamil': {
'id': 43,
'name': 'Tamil'
},
'indonesian': {
'id': 44,
'name': 'Indonesian'
},
'telugu': {
'id': 45,
'name': 'Telugu'
},
'macedonian': {
'id': 46,
'name': 'Macedonian'
},
'slovenian': {
'id': 47,
'name': 'Slovenian'
},
'malayalam': {
'id': 48,
'name': 'Malayalam'
},
'kannada': {
'id': 49,
'name': 'Kannada'
},
'albanian': {
'id': 50,
'name': 'Albanian'
},
'afrikaans': {
'id': 51,
'name': 'Afrikaans'
}
}
SONARR = {
'unknown': {
'id': 0,
'name': 'Unknown'
},
'english': {
'id': 1,
'name': 'English'
},
'french': {
'id': 2,
'name': 'French'
},
'spanish': {
'id': 3,
'name': 'Spanish'
},
'german': {
'id': 4,
'name': 'German'
},
'italian': {
'id': 5,
'name': 'Italian'
},
'danish': {
'id': 6,
'name': 'Danish'
},
'dutch': {
'id': 7,
'name': 'Dutch'
},
'japanese': {
'id': 8,
'name': 'Japanese'
},
'icelandic': {
'id': 9,
'name': 'Icelandic'
},
'chinese': {
'id': 10,
'name': 'Chinese'
},
'russian': {
'id': 11,
'name': 'Russian'
},
'polish': {
'id': 12,
'name': 'Polish'
},
'vietnamese': {
'id': 13,
'name': 'Vietnamese'
},
'swedish': {
'id': 14,
'name': 'Swedish'
},
'norwegian': {
'id': 15,
'name': 'Norwegian'
},
'finnish': {
'id': 16,
'name': 'Finnish'
},
'turkish': {
'id': 17,
'name': 'Turkish'
},
'portuguese': {
'id': 18,
'name': 'Portuguese'
},
'flemish': {
'id': 19,
'name': 'Flemish'
},
'greek': {
'id': 20,
'name': 'Greek'
},
'korean': {
'id': 21,
'name': 'Korean'
},
'hungarian': {
'id': 22,
'name': 'Hungarian'
},
'hebrew': {
'id': 23,
'name': 'Hebrew'
},
'lithuanian': {
'id': 24,
'name': 'Lithuanian'
},
'czech': {
'id': 25,
'name': 'Czech'
},
'arabic': {
'id': 26,
'name': 'Arabic'
},
'hindi': {
'id': 27,
'name': 'Hindi'
},
'bulgarian': {
'id': 28,
'name': 'Bulgarian'
},
'malayalam': {
'id': 29,
'name': 'Malayalam'
},
'ukrainian': {
'id': 30,
'name': 'Ukrainian'
},
'slovak': {
'id': 31,
'name': 'Slovak'
},
'thai': {
'id': 32,
'name': 'Thai'
},
'portuguese_br': {
'id': 33,
'name': 'Portuguese (Brazil)'
},
'spanish_latino': {
'id': 34,
'name': 'Spanish (Latino)'
},
'romanian': {
'id': 35,
'name': 'Romanian'
},
'latvian': {
'id': 36,
'name': 'Latvian'
},
'persian': {
'id': 37,
'name': 'Persian'
},
'catalan': {
'id': 38,
'name': 'Catalan'
},
'croatian': {
'id': 39,
'name': 'Croatian'
},
'serbian': {
'id': 40,
'name': 'Serbian'
},
'bosnian': {
'id': 41,
'name': 'Bosnian'
},
'estonian': {
'id': 42,
'name': 'Estonian'
},
'tamil': {
'id': 43,
'name': 'Tamil'
},
'indonesian': {
'id': 44,
'name': 'Indonesian'
},
'macedonian': {
'id': 45,
'name': 'Macedonian'
},
'slovenian': {
'id': 46,
'name': 'Slovenian'
},
'original': {
'id': -2,
'name': 'Original'
}
}
class QualityNameMapper:
"""Maps between different quality naming conventions"""
REMUX_MAPPINGS = {
TargetApp.SONARR: {
"Remux-1080p": "Bluray-1080p Remux",
"Remux-2160p": "Bluray-2160p Remux"
},
TargetApp.RADARR: {
"Remux-1080p": "Remux-1080p",
"Remux-2160p": "Remux-2160p"
}
}
ALTERNATE_NAMES = {
"BR-Disk": "BR-DISK",
"BR-DISK": "BR-DISK",
"BRDISK": "BR-DISK",
"BR_DISK": "BR-DISK",
"BLURAY-DISK": "BR-DISK",
"BLURAY_DISK": "BR-DISK",
"BLURAYDISK": "BR-DISK",
"Telecine": "TELECINE",
"TELECINE": "TELECINE",
"TeleCine": "TELECINE",
"Telesync": "TELESYNC",
"TELESYNC": "TELESYNC",
"TeleSync": "TELESYNC",
}
@classmethod
def map_quality_name(cls, name: str, target_app: TargetApp) -> str:
"""
Maps quality names between different formats based on target app
Args:
name: The quality name to map
target_app: The target application (RADARR or SONARR)
Returns:
The mapped quality name
"""
# Handle empty or None cases
if not name:
return name
# First check for remux mappings
if name in cls.REMUX_MAPPINGS.get(target_app, {}):
return cls.REMUX_MAPPINGS[target_app][name]
# Then check for alternate spellings
normalized_name = name.upper().replace("-", "").replace("_", "")
for alt_name, standard_name in cls.ALTERNATE_NAMES.items():
if normalized_name == alt_name.upper().replace("-", "").replace(
"_", ""):
return standard_name
return name
class LanguageNameMapper:
"""Maps between different language naming conventions"""
ALTERNATE_NAMES = {
"spanish-latino": "spanish_latino",
"spanish_latino": "spanish_latino",
"spanishlatino": "spanish_latino",
"portuguese-br": "portuguese_br",
"portuguese_br": "portuguese_br",
"portuguesebr": "portuguese_br",
"portuguese-brazil": "portuguese_br",
"portuguese_brazil": "portuguese_br"
}
@classmethod
def normalize_language_name(cls, name: str) -> str:
"""
Normalizes language names to a consistent format
Args:
name: The language name to normalize
Returns:
The normalized language name
"""
if not name:
return name
normalized = name.lower().replace(" ", "_")
return cls.ALTERNATE_NAMES.get(normalized, normalized)
class ValueResolver:
"""Helper class to resolve values based on target app"""
@classmethod
def get_indexer_flag(cls, flag: str, target_app: TargetApp) -> int:
flags = IndexerFlags.RADARR if target_app == TargetApp.RADARR else IndexerFlags.SONARR
return flags.get(flag.lower(), 0)
@classmethod
def get_source(cls, source: str, target_app: TargetApp) -> int:
sources = Sources.RADARR if target_app == TargetApp.RADARR else Sources.SONARR
return sources.get(source.lower(), 0)
@classmethod
def get_resolution(cls, resolution: str) -> int:
return Qualities.COMMON_RESOLUTIONS.get(resolution.lower(), 0)
@classmethod
def get_qualities(cls, target_app: TargetApp) -> Dict[str, Any]:
qualities = Qualities.RADARR if target_app == TargetApp.RADARR else Qualities.SONARR
return qualities
@classmethod
def get_quality_name(cls, name: str, target_app: TargetApp) -> str:
"""Maps quality names between different formats based on target app"""
return QualityNameMapper.map_quality_name(name, target_app)
@classmethod
def get_language(cls,
language_name: str,
target_app: TargetApp,
for_profile: bool = True) -> Dict[str, Any]:
"""
Get language mapping based on target app and context
Args:
language_name: Name of the language to look up
target_app: Target application (RADARR or SONARR)
for_profile: If True, this is for a quality profile. If False, this is for a custom format.
"""
languages = Languages.RADARR if target_app == TargetApp.RADARR else Languages.SONARR
# For profiles, only Radarr uses language settings
if for_profile and target_app == TargetApp.SONARR:
return {'id': -2, 'name': 'Original'}
# Normalize the language name
normalized_name = LanguageNameMapper.normalize_language_name(
language_name)
language_data = languages.get(normalized_name)
if not language_data:
logger.warning(
f"Language '{language_name}' (normalized: '{normalized_name}') "
f"not found in {target_app} mappings, falling back to Unknown")
language_data = languages['unknown']
return language_data