mirror of
https://github.com/ldericher/fftcgtool
synced 2025-01-15 23:03:00 +00:00
fallback for nonexistent images, verbosity, logging output
This commit is contained in:
parent
ba58a99690
commit
0f6c7be504
5 changed files with 60 additions and 21 deletions
|
@ -19,11 +19,11 @@ class Book:
|
||||||
|
|
||||||
# all card face URLs
|
# all card face URLs
|
||||||
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
|
for card in cards
|
||||||
]
|
]
|
||||||
# card back URL
|
# card back URL
|
||||||
urls.append(CARD_BACK_URL)
|
urls.append((CARD_BACK_URL, "", ""))
|
||||||
|
|
||||||
# multi-threaded download
|
# multi-threaded download
|
||||||
images = ImageLoader.load(urls, num_threads)
|
images = ImageLoader.load(urls, num_threads)
|
||||||
|
|
|
@ -1,33 +1,46 @@
|
||||||
import io
|
|
||||||
import logging
|
import logging
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from .language import Language
|
||||||
from .utils import RESOLUTION
|
from .utils import RESOLUTION
|
||||||
|
|
||||||
|
# constants
|
||||||
|
FALLBACK_LANGUAGE = Language("en")
|
||||||
|
|
||||||
|
|
||||||
class ImageLoader:
|
class ImageLoader:
|
||||||
@classmethod
|
@classmethod
|
||||||
def _load(cls, url: str) -> Image.Image:
|
def _load_inner(cls, url_parts: tuple[str, str, str]) -> Image.Image:
|
||||||
logger = logging.getLogger(__name__)
|
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)
|
# fetch image (retry on fail)
|
||||||
while True:
|
while True:
|
||||||
logger.info(f"downloading image {url}")
|
|
||||||
try:
|
try:
|
||||||
res = requests.get(url)
|
res = requests.get(url, stream=True)
|
||||||
image = Image.open(io.BytesIO(res.content))
|
break
|
||||||
|
|
||||||
|
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
|
# unify images
|
||||||
|
image = Image.open(res.raw)
|
||||||
image.convert(mode="RGB")
|
image.convert(mode="RGB")
|
||||||
return image.resize(RESOLUTION, Image.BICUBIC)
|
return image.resize(RESOLUTION, Image.BICUBIC)
|
||||||
|
|
||||||
except requests.exceptions.RequestException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
@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:
|
with multiprocessing.Pool(num_threads) as p:
|
||||||
return p.map(ImageLoader._load, urls)
|
return p.map(cls._load_inner, urls_parts)
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
class Language:
|
class Language:
|
||||||
def __init__(self, language: str):
|
def __init__(self, language: str):
|
||||||
language = language.lower()
|
language = language.lower()
|
||||||
|
@ -15,6 +18,12 @@ class Language:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.__short
|
return self.__short
|
||||||
|
|
||||||
|
def __hash__(self) -> hash:
|
||||||
|
return hash(str(self))
|
||||||
|
|
||||||
|
def __eq__(self, other: Language):
|
||||||
|
return str(self) == str(other)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_suffix(self):
|
def image_suffix(self):
|
||||||
# supported languages for face URLs
|
# supported languages for face URLs
|
||||||
|
@ -32,4 +41,3 @@ class Language:
|
||||||
return ""
|
return ""
|
||||||
else:
|
else:
|
||||||
return "_EN"
|
return "_EN"
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ from .ttsdeck import TTSDeck
|
||||||
|
|
||||||
|
|
||||||
class Opus(Cards):
|
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):
|
def __init__(self, opus_id: str, language: Language):
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -64,7 +64,7 @@ class Opus(Cards):
|
||||||
self.sort(key=lambda x: x.code.opus)
|
self.sort(key=lambda x: x.code.opus)
|
||||||
|
|
||||||
for card in self:
|
for card in self:
|
||||||
logger.info(f"imported card {card}")
|
logger.debug(f"imported card {card}")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def number(self) -> str:
|
def number(self) -> str:
|
||||||
|
|
26
main.py
26
main.py
|
@ -47,6 +47,12 @@ def main() -> None:
|
||||||
description="Imports FFTCG cards for TT-Sim.",
|
description="Imports FFTCG cards for TT-Sim.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose",
|
||||||
|
help="increase output verbosity",
|
||||||
|
action="count",
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-l", "--language",
|
"-l", "--language",
|
||||||
type=fftcg.Language,
|
type=fftcg.Language,
|
||||||
|
@ -105,12 +111,25 @@ def main() -> None:
|
||||||
help="the Deck to import",
|
help="the Deck to import",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# parse arguments
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
# set up logging
|
# 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(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=args.verbose,
|
||||||
format="%(levelname)s: %(processName)s %(message)s",
|
format="%(levelname)s: %(processName)s %(message)s",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.debug(f"{args = }")
|
||||||
|
|
||||||
# output directory
|
# output directory
|
||||||
if not os.path.exists(OUT_DIR_NAME):
|
if not os.path.exists(OUT_DIR_NAME):
|
||||||
os.mkdir(OUT_DIR_NAME)
|
os.mkdir(OUT_DIR_NAME)
|
||||||
|
@ -118,13 +137,12 @@ def main() -> None:
|
||||||
os.chdir(OUT_DIR_NAME)
|
os.chdir(OUT_DIR_NAME)
|
||||||
|
|
||||||
# call function based on args
|
# call function based on args
|
||||||
args = parser.parse_args()
|
|
||||||
for deck in args.func(args):
|
for deck in args.func(args):
|
||||||
deck.save()
|
deck.save()
|
||||||
|
|
||||||
# bye
|
# bye
|
||||||
logging.info("Done. Put the generated JSON files in your 'Saved Objects' Folder.")
|
print("Done. Put the generated JSON files in your 'Saved Objects' Folder.")
|
||||||
logging.info("Thanks for using fftcgtool!")
|
print("Thanks for using fftcgtool!")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
Loading…
Reference in a new issue