conversion_project/core/config_helper.py
2026-08-22 21:01:42 -04:00

202 lines
8.0 KiB
Python

import xml.etree.ElementTree as ET
from pathlib import Path
# Default XML content to write if missing
DEFAULT_XML = """<?xml version="1.0" encoding="UTF-8"?>
<config>
<general>
<processing_folder>processing</processing_folder>
<suffix> -EHX</suffix>
<extensions>.mkv,.mp4</extensions>
<ignore_tags>ehx,megusta</ignore_tags>
<reduction_ratio_threshold>0.5</reduction_ratio_threshold>
</general>
<path_mappings>
<map from="P:\\tv" to="/mnt/plex/tv" />
<map from="P:\\anime" to="/mnt/plex/anime" />
</path_mappings>
<encode>
<cq>
<tv_1080>28</tv_1080>
<tv_720>32</tv_720>
<movie_1080>32</movie_1080>
<movie_720>34</movie_720>
</cq>
<fallback>
<bitrate_1080>1500k</bitrate_1080>
<maxrate_1080>1750k</maxrate_1080>
<bufsize_1080>2750k</bufsize_1080>
<bitrate_720>900k</bitrate_720>
<maxrate_720>1250k</maxrate_720>
<bufsize_720>1800k</bufsize_720>
</fallback>
<filters>
<default>lanczos</default>
<tv>bicubic</tv>
</filters>
</encode>
<audio>
<stereo>
<low>64000</low>
<medium>128000</medium>
<high>160000</high>
</stereo>
<multi_channel>
<low>384000</low>
<medium>512000</medium>
<high>640000</high>
</multi_channel>
</audio>
</config>
"""
def load_config_xml(path: Path) -> dict:
if not path.exists():
path.write_text(DEFAULT_XML, encoding="utf-8")
print(f"[INFO] Created default config.xml at {path}")
tree = ET.parse(path)
root = tree.getroot()
# --- General ---
general = root.find("general")
processing_folder_elem = general.find("processing_folder") if general is not None else None
processing_folder = processing_folder_elem.text if processing_folder_elem is not None else "processing"
suffix_elem = general.find("suffix") if general is not None else None
suffix = suffix_elem.text if suffix_elem is not None else " -EHX"
extensions_elem = general.find("extensions") if general is not None else None
extensions = extensions_elem.text.split(",") if extensions_elem is not None else [".mkv", ".mp4"]
ignore_tags_elem = general.find("ignore_tags") if general is not None else None
ignore_tags = ignore_tags_elem.text.split(",") if ignore_tags_elem is not None else ["ehx", "megusta"]
reduction_ratio_elem = general.find("reduction_ratio_threshold") if general is not None else None
reduction_ratio_threshold = float(reduction_ratio_elem.text) if reduction_ratio_elem is not None else 0.5
# Extract general section as nested dict for other settings
general_dict = {}
if general is not None:
# Subtitles
subtitles_elem = general.find("subtitles")
if subtitles_elem is not None:
general_dict["subtitles"] = {
"enabled": subtitles_elem.find("enabled").text.lower() == "true" if subtitles_elem.find("enabled") is not None else True,
"extensions": subtitles_elem.find("extensions").text if subtitles_elem.find("extensions") is not None else ".vtt,.srt,.ass,.ssa,.sub",
"codec": subtitles_elem.find("codec").text if subtitles_elem.find("codec") is not None else "srt"
}
# Audio filter
audio_filter_elem = general.find("audio_filter")
if audio_filter_elem is not None:
general_dict["audio_filter"] = {
"enabled": audio_filter_elem.find("enabled").text.lower() == "true" if audio_filter_elem.find("enabled") is not None else False
}
# Default language for undefined audio tracks
default_language_elem = general.find("default_language")
if default_language_elem is not None:
general_dict["default_language"] = default_language_elem.text if default_language_elem.text else "eng"
# --- Path Mappings ---
path_mappings = []
for m in root.findall("path_mappings/map"):
f = m.attrib.get("from")
t = m.attrib.get("to")
if f and t:
path_mappings.append({"from": f, "to": t})
# --- Encode ---
encode_elem = root.find("encode")
cq = {}
fallback = {}
filters = {}
if encode_elem is not None:
cq_elem = encode_elem.find("cq")
if cq_elem is not None:
# Check if CQ has encoder-specific sub-elements (av1, hevc)
encoder_elems = list(cq_elem)
if encoder_elems and encoder_elems[0].tag in ["av1", "hevc"]:
# New nested structure with encoder-specific CQ values
for encoder_tag in cq_elem:
if encoder_tag.tag in ["av1", "hevc"]:
cq[encoder_tag.tag] = {}
for child in encoder_tag:
if child.text and child.text.strip():
cq[encoder_tag.tag][child.tag] = int(child.text.strip())
else:
# Old flat structure (backwards compatibility)
for child in cq_elem:
if child.text and child.text.strip():
cq[child.tag] = int(child.text.strip())
fallback_elem = encode_elem.find("fallback")
if fallback_elem is not None:
for child in fallback_elem:
if child.text and child.text.strip():
fallback[child.tag] = child.text.strip()
filters_elem = encode_elem.find("filters")
if filters_elem is not None:
for child in filters_elem:
if child.text and child.text.strip():
filters[child.tag] = child.text.strip()
# --- Audio ---
audio = {"stereo": {}, "multi_channel": {}, "surround_8ch": {}}
stereo_elem = root.find("audio/stereo")
if stereo_elem is not None:
for child in stereo_elem:
if child.text and child.text.strip():
audio["stereo"][child.tag] = int(child.text.strip())
multi_elem = root.find("audio/multi_channel")
if multi_elem is not None:
for child in multi_elem:
if child.text and child.text.strip():
audio["multi_channel"][child.tag] = int(child.text.strip())
surround_8ch_elem = root.find("audio/surround_8ch")
if surround_8ch_elem is not None:
for child in surround_8ch_elem:
if child.text and child.text.strip():
audio["surround_8ch"][child.tag] = int(child.text.strip())
# --- Services (Sonarr/Radarr) ---
services = {"sonarr": {}, "radarr": {}}
sonarr_elem = root.find("services/sonarr")
if sonarr_elem is not None:
url_elem = sonarr_elem.find("url")
api_elem = sonarr_elem.find("api_key")
rg_elem = sonarr_elem.find("new_release_group")
services["sonarr"] = {
"url": url_elem.text if url_elem is not None and url_elem.text else None,
"api_key": api_elem.text if api_elem is not None and api_elem.text else None,
"new_release_group": rg_elem.text if rg_elem is not None and rg_elem.text else "CONVERTED"
}
radarr_elem = root.find("services/radarr")
if radarr_elem is not None:
url_elem = radarr_elem.find("url")
api_elem = radarr_elem.find("api_key")
rg_elem = radarr_elem.find("new_release_group")
services["radarr"] = {
"url": url_elem.text if url_elem is not None and url_elem.text else None,
"api_key": api_elem.text if api_elem is not None and api_elem.text else None,
"new_release_group": rg_elem.text if rg_elem is not None and rg_elem.text else "CONVERTED"
}
return {
"processing_folder": processing_folder,
"suffix": suffix,
"extensions": [ext.lower() for ext in extensions],
"ignore_tags": [tag.strip() for tag in ignore_tags],
"reduction_ratio_threshold": reduction_ratio_threshold,
"path_mappings": path_mappings,
"general": general_dict,
"encode": {"cq": cq, "fallback": fallback, "filters": filters},
"audio": audio,
"services": services
}