89 lines
2.8 KiB
Python
89 lines
2.8 KiB
Python
"""Download settings and file-type handling."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
|
|
from sticker_downloader.errors import InvalidFileType
|
|
|
|
STATIC = "webp"
|
|
ANIMATED = "tgs"
|
|
VIDEO = "webm"
|
|
THUMBNAIL = "png"
|
|
|
|
#: Every type the downloader knows how to save, in a stable display order.
|
|
ALL_FILE_TYPES: tuple[str, ...] = (STATIC, ANIMATED, VIDEO, THUMBNAIL)
|
|
|
|
FILE_TYPE_HELP: dict[str, str] = {
|
|
STATIC: "static stickers",
|
|
ANIMATED: "animated stickers (Lottie)",
|
|
VIDEO: "video stickers",
|
|
THUMBNAIL: "PNG thumbnails",
|
|
}
|
|
|
|
DEFAULT_OUTPUT_DIR = Path("downloads")
|
|
DEFAULT_URL_FILE = Path("urls.txt")
|
|
DEFAULT_CONCURRENCY = 8
|
|
DEFAULT_RETRIES = 3
|
|
METADATA_FILE_NAME = "pack.json"
|
|
PACK_THUMBNAIL_STEM = "_pack_thumbnail"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DownloadConfig:
|
|
"""Everything that shapes a download run."""
|
|
|
|
file_types: frozenset[str] = field(default_factory=lambda: frozenset(ALL_FILE_TYPES))
|
|
output_dir: Path = DEFAULT_OUTPUT_DIR
|
|
concurrency: int = DEFAULT_CONCURRENCY
|
|
retries: int = DEFAULT_RETRIES
|
|
overwrite: bool = False
|
|
write_metadata: bool = True
|
|
pack_thumbnail: bool = True
|
|
emoji_names: bool = False
|
|
dry_run: bool = False
|
|
|
|
def __post_init__(self) -> None:
|
|
unknown = sorted(self.file_types - set(ALL_FILE_TYPES))
|
|
if unknown:
|
|
raise InvalidFileType(f"unknown file type(s): {', '.join(unknown)}")
|
|
if not self.file_types:
|
|
raise InvalidFileType("at least one file type is required")
|
|
if self.concurrency < 1:
|
|
raise ValueError("concurrency must be at least 1")
|
|
if self.retries < 1:
|
|
raise ValueError("retries must be at least 1")
|
|
|
|
@property
|
|
def sorted_file_types(self) -> list[str]:
|
|
"""The requested types in canonical order, for stable output."""
|
|
return [file_type for file_type in ALL_FILE_TYPES if file_type in self.file_types]
|
|
|
|
def wants(self, file_type: str) -> bool:
|
|
return file_type in self.file_types
|
|
|
|
|
|
def parse_file_types(raw: str | None) -> frozenset[str]:
|
|
"""Turn user input such as ``"webp, png"``, ``"all"`` or ``""`` into a type set.
|
|
|
|
Empty input and ``"all"`` both mean "every supported type", matching what the
|
|
interactive prompt advertises.
|
|
"""
|
|
text = (raw or "").strip().lower()
|
|
if not text or text == "all":
|
|
return frozenset(ALL_FILE_TYPES)
|
|
|
|
requested = [part.strip().lstrip(".") for part in text.replace(" ", ",").split(",")]
|
|
selected = {part for part in requested if part}
|
|
if not selected:
|
|
raise InvalidFileType("no file types given")
|
|
|
|
unknown = sorted(selected - set(ALL_FILE_TYPES))
|
|
if unknown:
|
|
raise InvalidFileType(
|
|
f"unknown file type(s): {', '.join(unknown)}. "
|
|
f"Supported: {', '.join(ALL_FILE_TYPES)}, or 'all'."
|
|
)
|
|
return frozenset(selected)
|