~cytrogen/kobo-manga

ref: 4e504823f4bf8d2b5f4279da3f4d4ebe98fc97ad kobo-manga/src/kobo_manga/config.py -rw-r--r-- 2.3 KiB
4e504823 — HallowDem Initial commit: kobo-manga pipeline a day ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
"""配置加载模块"""

from dataclasses import dataclass, field
from pathlib import Path

import yaml


@dataclass
class DeviceConfig:
    model: str = "kobo-clara-bw"
    width: int = 1072
    height: int = 1448
    color: bool = False


@dataclass
class ProcessingConfig:
    split_double_page: bool = True
    crop_whitespace: bool = True
    resize: bool = True
    grayscale: bool = True
    enhance_contrast: bool = True
    contrast_factor: float = 1.2


@dataclass
class DownloadConfig:
    concurrent: int = 3
    retry: int = 3
    delay: float = 1.0


@dataclass
class TransferConfig:
    method: str = "usb"
    calibre_host: str = "localhost"
    calibre_port: int = 8080


@dataclass
class SchedulerConfig:
    interval: int = 3600
    auto_push: bool = False


@dataclass
class AppConfig:
    device: DeviceConfig = field(default_factory=DeviceConfig)
    sources: list[str] = field(default_factory=list)
    processing: ProcessingConfig = field(default_factory=ProcessingConfig)
    download: DownloadConfig = field(default_factory=DownloadConfig)
    transfer: TransferConfig = field(default_factory=TransferConfig)
    scheduler: SchedulerConfig = field(default_factory=SchedulerConfig)


def _dict_to_dataclass(cls, data: dict):
    """将字典映射到 dataclass,忽略多余字段。"""
    if data is None:
        return cls()
    valid_fields = {f.name for f in cls.__dataclass_fields__.values()}
    filtered = {k: v for k, v in data.items() if k in valid_fields}
    return cls(**filtered)


def load_config(config_path: str | Path | None = None) -> AppConfig:
    """加载配置文件,未指定时查找项目根目录的 config.yaml。"""
    if config_path is None:
        config_path = Path("config.yaml")
    else:
        config_path = Path(config_path)

    if not config_path.exists():
        return AppConfig()

    with open(config_path, encoding="utf-8") as f:
        raw = yaml.safe_load(f) or {}

    return AppConfig(
        device=_dict_to_dataclass(DeviceConfig, raw.get("device")),
        sources=raw.get("sources", []),
        processing=_dict_to_dataclass(ProcessingConfig, raw.get("processing")),
        download=_dict_to_dataclass(DownloadConfig, raw.get("download")),
        transfer=_dict_to_dataclass(TransferConfig, raw.get("transfer")),
        scheduler=_dict_to_dataclass(SchedulerConfig, raw.get("scheduler")),
    )