202 lines
8.0 KiB
Python
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
|
|
}
|