2023-02-13 19:15:27 +01:00
|
|
|
from pathlib import Path
|
2023-02-20 14:03:40 +01:00
|
|
|
from typing import Any, Dict, Optional, Tuple, TypedDict, Union
|
2023-02-13 19:15:27 +01:00
|
|
|
|
|
|
|
import xmltodict
|
|
|
|
from loguru import logger as log
|
|
|
|
|
2023-02-15 14:21:45 +01:00
|
|
|
METADATA_FILENAME = "ComicInfo.xml"
|
2023-02-15 13:50:59 +01:00
|
|
|
METADATA_TEMPLATE = Path("mangadlp/metadata/ComicInfo_v2.0.xml")
|
2023-02-15 14:21:45 +01:00
|
|
|
# define metadata types, defaults and valid values. an empty list means no value check
|
|
|
|
# {key: (type, default value, valid values)}
|
2023-02-18 16:21:03 +01:00
|
|
|
METADATA_TYPES: Dict[str, Tuple[type, Any, list[Union[str, int]]]] = {
|
2023-02-15 13:50:59 +01:00
|
|
|
"Title": (str, None, []),
|
|
|
|
"Series": (str, None, []),
|
|
|
|
"Number": (str, None, []),
|
|
|
|
"Count": (int, None, []),
|
|
|
|
"Volume": (int, None, []),
|
|
|
|
"AlternateSeries": (str, None, []),
|
|
|
|
"AlternateNumber": (str, None, []),
|
|
|
|
"AlternateCount": (int, None, []),
|
|
|
|
"Summary": (str, None, []),
|
|
|
|
"Notes": (str, "Downloaded with https://github.com/olofvndrhr/manga-dlp", []),
|
|
|
|
"Year": (int, None, []),
|
|
|
|
"Month": (int, None, []),
|
|
|
|
"Day": (int, None, []),
|
|
|
|
"Writer": (str, None, []),
|
|
|
|
"Colorist": (str, None, []),
|
|
|
|
"Publisher": (str, None, []),
|
|
|
|
"Genre": (str, None, []),
|
|
|
|
"Web": (str, None, []),
|
|
|
|
"PageCount": (int, None, []),
|
|
|
|
"LanguageISO": (str, None, []),
|
|
|
|
"Format": (str, None, []),
|
|
|
|
"BlackAndWhite": (str, None, ["Yes", "No", "Unknown"]),
|
|
|
|
"Manga": (str, "Yes", ["Yes", "No", "Unknown", "YesAndRightToLeft"]),
|
|
|
|
"ScanInformation": (str, None, []),
|
|
|
|
"SeriesGroup": (str, None, []),
|
|
|
|
"AgeRating": (
|
|
|
|
str,
|
|
|
|
None,
|
|
|
|
[
|
|
|
|
"Unknown",
|
|
|
|
"Adults Only 18+",
|
|
|
|
"Early Childhood",
|
|
|
|
"Everyone",
|
|
|
|
"Everyone 10+",
|
|
|
|
"G",
|
|
|
|
"Kids to Adults",
|
|
|
|
"M",
|
|
|
|
"MA15+",
|
|
|
|
"Mature 17+",
|
|
|
|
"PG",
|
|
|
|
"R18+",
|
|
|
|
"Rating Pending",
|
|
|
|
"Teen",
|
|
|
|
"X18+",
|
|
|
|
],
|
|
|
|
),
|
|
|
|
"CommunityRating": (int, None, [1, 2, 3, 4, 5]),
|
|
|
|
}
|
2023-02-13 19:15:27 +01:00
|
|
|
|
2023-02-13 23:17:52 +01:00
|
|
|
|
2023-02-20 14:03:40 +01:00
|
|
|
class ComicInfo(TypedDict, total=False):
|
|
|
|
"""ComicInfo.xml basic types.
|
|
|
|
|
|
|
|
Validation is done via metadata.validate_metadata()
|
|
|
|
All valid types and values are specified in metadata.METADATA_TYPES
|
|
|
|
"""
|
|
|
|
|
|
|
|
Title: Optional[str]
|
|
|
|
Series: Optional[str]
|
|
|
|
Number: Optional[str]
|
|
|
|
Count: Optional[int]
|
|
|
|
Volume: Optional[int]
|
|
|
|
AlternateSeries: Optional[str]
|
|
|
|
AlternateNumber: Optional[str]
|
|
|
|
AlternateCount: Optional[int]
|
|
|
|
Summary: Optional[str]
|
|
|
|
Notes: Optional[str]
|
|
|
|
Year: Optional[int]
|
|
|
|
Month: Optional[int]
|
|
|
|
Day: Optional[int]
|
|
|
|
Writer: Optional[str]
|
|
|
|
Colorist: Optional[str]
|
|
|
|
Publisher: Optional[str]
|
|
|
|
Genre: Optional[str]
|
|
|
|
Web: Optional[str]
|
|
|
|
PageCount: Optional[int]
|
|
|
|
LanguageISO: Optional[str]
|
|
|
|
Format: Optional[str]
|
|
|
|
BlackAndWhite: Optional[str]
|
|
|
|
Manga: Optional[str]
|
|
|
|
ScanInformation: Optional[str]
|
|
|
|
SeriesGroup: Optional[str]
|
|
|
|
AgeRating: Optional[str]
|
|
|
|
CommunityRating: Optional[int]
|
|
|
|
|
|
|
|
|
|
|
|
class ChapterData(TypedDict):
|
|
|
|
"""Basic chapter-data types.
|
|
|
|
|
|
|
|
All values have to be provided.
|
|
|
|
"""
|
|
|
|
|
|
|
|
uuid: str
|
|
|
|
volume: str
|
|
|
|
chapter: str
|
|
|
|
name: str
|
|
|
|
pages: int
|
|
|
|
|
|
|
|
|
|
|
|
def validate_metadata(metadata_in: ComicInfo) -> Dict[str, ComicInfo]:
|
2023-02-15 13:50:59 +01:00
|
|
|
log.info("Validating metadata")
|
2023-02-13 19:15:27 +01:00
|
|
|
|
2023-02-20 14:03:40 +01:00
|
|
|
metadata_valid: dict[str, ComicInfo] = {"ComicInfo": {}}
|
2023-02-15 13:50:59 +01:00
|
|
|
for key, value in METADATA_TYPES.items():
|
|
|
|
metadata_type, metadata_default, metadata_validation = value
|
|
|
|
|
|
|
|
# add default value if present
|
|
|
|
if metadata_default:
|
2023-02-15 16:47:43 +01:00
|
|
|
log.debug(
|
|
|
|
f"Setting default value for Key:{key} -> value={metadata_default}"
|
|
|
|
)
|
2023-02-15 13:50:59 +01:00
|
|
|
metadata_valid["ComicInfo"][key] = metadata_default
|
|
|
|
|
|
|
|
# check if metadata key is available
|
2023-02-13 19:15:27 +01:00
|
|
|
try:
|
2023-02-20 14:03:40 +01:00
|
|
|
md_to_check: Union[str, int, None] = metadata_in[key]
|
2023-02-13 19:15:27 +01:00
|
|
|
except KeyError:
|
|
|
|
continue
|
2023-02-15 13:50:59 +01:00
|
|
|
# check if provided metadata item is empty
|
|
|
|
if not md_to_check:
|
|
|
|
continue
|
|
|
|
|
2023-02-14 13:31:55 +01:00
|
|
|
# check if metadata type is correct
|
2023-02-15 13:50:59 +01:00
|
|
|
log.debug(f"Key:{key} -> value={type(md_to_check)} -> check={metadata_type}")
|
2023-02-18 12:29:25 +01:00
|
|
|
if not isinstance(md_to_check, metadata_type):
|
2023-02-15 13:50:59 +01:00
|
|
|
log.warning(
|
|
|
|
f"Metadata has wrong type: {key}:{metadata_type} -> {md_to_check}"
|
|
|
|
)
|
|
|
|
continue
|
|
|
|
|
|
|
|
# check if metadata is valid
|
|
|
|
log.debug(f"Key:{key} -> value={md_to_check} -> valid={metadata_validation}")
|
|
|
|
if (len(metadata_validation) > 0) and (md_to_check not in metadata_validation):
|
2023-02-14 13:31:55 +01:00
|
|
|
log.warning(
|
2023-02-15 13:50:59 +01:00
|
|
|
f"Metadata is invalid: {key}:{metadata_validation} -> {md_to_check}"
|
2023-02-14 13:31:55 +01:00
|
|
|
)
|
|
|
|
continue
|
|
|
|
|
2023-02-15 13:50:59 +01:00
|
|
|
log.debug(f"Updating metadata: '{key}' = '{md_to_check}'")
|
|
|
|
metadata_valid["ComicInfo"][key] = md_to_check
|
|
|
|
|
|
|
|
return metadata_valid
|
|
|
|
|
|
|
|
|
2023-02-20 14:03:40 +01:00
|
|
|
def write_metadata(chapter_path: Path, metadata: ComicInfo) -> None:
|
|
|
|
if metadata["Format"] == "pdf": # pyright:ignore
|
2023-02-15 13:50:59 +01:00
|
|
|
log.warning("Can't add metadata for pdf format. Skipping")
|
|
|
|
return
|
|
|
|
|
2023-02-15 14:21:45 +01:00
|
|
|
metadata_file = chapter_path / METADATA_FILENAME
|
2023-02-13 19:15:27 +01:00
|
|
|
|
2023-02-15 13:50:59 +01:00
|
|
|
log.debug(f"Metadata items: {metadata}")
|
|
|
|
metadata_valid = validate_metadata(metadata)
|
|
|
|
|
|
|
|
log.info(f"Writing metadata to: '{metadata_file}'")
|
|
|
|
metadata_export = xmltodict.unparse(
|
|
|
|
metadata_valid, pretty=True, indent=" " * 4, short_empty_elements=True
|
|
|
|
)
|
2023-02-13 23:17:52 +01:00
|
|
|
metadata_file.touch(exist_ok=True)
|
2023-02-13 19:15:27 +01:00
|
|
|
metadata_file.write_text(metadata_export, encoding="utf8")
|