diff --git a/fftcg/book.py b/fftcg/book.py index 6dc5ba4..8b70d39 100644 --- a/fftcg/book.py +++ b/fftcg/book.py @@ -19,11 +19,11 @@ class Book: # all card face URLs urls = [ - f"https://fftcg.cdn.sewest.net/images/cards/full/{card.code}_{language.image_suffix}.jpg" + ("https://fftcg.cdn.sewest.net/images/cards/full/{}_{}.jpg", str(card.code), language.image_suffix) for card in cards ] # card back URL - urls.append(CARD_BACK_URL) + urls.append((CARD_BACK_URL, "", "")) # multi-threaded download images = ImageLoader.load(urls, num_threads) diff --git a/fftcg/imageloader.py b/fftcg/imageloader.py index 22101a0..51f52b9 100644 --- a/fftcg/imageloader.py +++ b/fftcg/imageloader.py @@ -1,33 +1,46 @@ -import io import logging import multiprocessing import requests from PIL import Image +from .language import Language from .utils import RESOLUTION +# constants +FALLBACK_LANGUAGE = Language("en") + class ImageLoader: @classmethod - def _load(cls, url: str) -> Image.Image: + def _load_inner(cls, url_parts: tuple[str, str, str]) -> Image.Image: logger = logging.getLogger(__name__) + base_url, code, lang_suffix = url_parts + + # put together image url + url = base_url.format(code, lang_suffix) + logger.info(f"trying image {url}") # fetch image (retry on fail) while True: - logger.info(f"downloading image {url}") try: - res = requests.get(url) - image = Image.open(io.BytesIO(res.content)) + res = requests.get(url, stream=True) + break - # unify images - image.convert(mode="RGB") - return image.resize(RESOLUTION, Image.BICUBIC) - - except requests.exceptions.RequestException: + except requests.RequestException: pass + # if rejected, substitute the english version + if not res.status_code == 200: + logger.warning(f"falling back to english version of {url}") + return cls._load_inner((base_url, code, FALLBACK_LANGUAGE.image_suffix)) + + # unify images + image = Image.open(res.raw) + image.convert(mode="RGB") + return image.resize(RESOLUTION, Image.BICUBIC) + @classmethod - def load(cls, urls: list[str], num_threads: int) -> list[Image.Image]: + def load(cls, urls_parts: list[tuple[str, str, str]], num_threads: int) -> list[Image.Image]: with multiprocessing.Pool(num_threads) as p: - return p.map(ImageLoader._load, urls) + return p.map(cls._load_inner, urls_parts) diff --git a/fftcg/language.py b/fftcg/language.py index ec15b7b..548fbe4 100644 --- a/fftcg/language.py +++ b/fftcg/language.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class Language: def __init__(self, language: str): language = language.lower() @@ -15,6 +18,12 @@ class Language: def __str__(self): return self.__short + def __hash__(self) -> hash: + return hash(str(self)) + + def __eq__(self, other: Language): + return str(self) == str(other) + @property def image_suffix(self): # supported languages for face URLs @@ -32,4 +41,3 @@ class Language: return "" else: return "_EN" - diff --git a/fftcg/opus.py b/fftcg/opus.py index d15828b..b768423 100644 --- a/fftcg/opus.py +++ b/fftcg/opus.py @@ -10,7 +10,7 @@ from .ttsdeck import TTSDeck class Opus(Cards): - __SQUARE_API_URL = "https://fftcg.square-enix-games.com/de/get-cards" + __SQUARE_API_URL = "https://fftcg.square-enix-games.com/en/get-cards" def __init__(self, opus_id: str, language: Language): logger = logging.getLogger(__name__) @@ -64,7 +64,7 @@ class Opus(Cards): self.sort(key=lambda x: x.code.opus) for card in self: - logger.info(f"imported card {card}") + logger.debug(f"imported card {card}") @property def number(self) -> str: diff --git a/main.py b/main.py index 7388a3f..81ba907 100755 --- a/main.py +++ b/main.py @@ -47,6 +47,12 @@ def main() -> None: description="Imports FFTCG cards for TT-Sim.", ) + parser.add_argument( + "-v", "--verbose", + help="increase output verbosity", + action="count", + ) + parser.add_argument( "-l", "--language", type=fftcg.Language, @@ -105,12 +111,25 @@ def main() -> None: help="the Deck to import", ) + # parse arguments + args = parser.parse_args() + # set up logging + if args.verbose is None: + args.verbose = logging.WARN + elif args.verbose == 1: + args.verbose = logging.INFO + else: + args.verbose = logging.DEBUG + logging.basicConfig( - level=logging.INFO, + level=args.verbose, format="%(levelname)s: %(processName)s %(message)s", ) + logger = logging.getLogger(__name__) + logger.debug(f"{args = }") + # output directory if not os.path.exists(OUT_DIR_NAME): os.mkdir(OUT_DIR_NAME) @@ -118,13 +137,12 @@ def main() -> None: os.chdir(OUT_DIR_NAME) # call function based on args - args = parser.parse_args() for deck in args.func(args): deck.save() # bye - logging.info("Done. Put the generated JSON files in your 'Saved Objects' Folder.") - logging.info("Thanks for using fftcgtool!") + print("Done. Put the generated JSON files in your 'Saved Objects' Folder.") + print("Thanks for using fftcgtool!") if __name__ == "__main__":