diff --git a/fftcg/book.py b/fftcg/book.py index 2a65ffb..ab09f62 100644 --- a/fftcg/book.py +++ b/fftcg/book.py @@ -15,25 +15,49 @@ def chunks(whole: list, chunk_size): class Book: + # Card faces by Square API + __FACE_URL = "https://fftcg.cdn.sewest.net/images/cards/full/{}_{}.jpg" + + # Card back image by Aurik + __BACK_URL = "http://cloud-3.steamusercontent.com/ugc/948455238665576576/85063172B8C340602E8D6C783A457122F53F7843/" + def __init__(self, cards, grid, resolution, language, num_threads): logger = logging.getLogger(__name__) - images = ImageLoader.load(cards, resolution, language, num_threads) + # all card face URLs + urls = [Book.__FACE_URL.format(card.code, language) for card in cards] + # card back URL + urls.append(Book.__BACK_URL) + + # multithreaded download + images = ImageLoader.load(urls, resolution, language, num_threads) + # card back Image + back_image = images.pop(-1) # shorthands # rows and columns per sheet r, c = grid + # capacity of grid (reserve last space for card back) + grid_capacity = r * c - 1 # width, height per card w, h = resolution + def paste_image(page, index, image): + x, y = (index % c) * w, (index // c) * h + page.paste(image, (x, y)) + self.__pages = [] - for images in chunks(images, r * c - 1): + for images in chunks(images, grid_capacity): + # create book page Image page = Image.new("RGB", (c * w, r * h)) logger.info(f"New image: {page.size[0]}x{page.size[1]}") + # paste card faces onto page for i, image in enumerate(images): - x, y = (i % c) * w, (i // c) * h - page.paste(image, (x, y)) + paste_image(page, i, image) + + # paste card back in last position + paste_image(page, c * r - 1, back_image) self.__pages.append(page) diff --git a/fftcg/imageloader.py b/fftcg/imageloader.py index 27cc0a7..e556fb7 100644 --- a/fftcg/imageloader.py +++ b/fftcg/imageloader.py @@ -8,16 +8,10 @@ from PIL import Image class ImageLoader(threading.Thread): - # Card faces by Square API - __FACE_URL = "https://fftcg.cdn.sewest.net/images/cards/full/{}_{}.jpg" - - # Card back image by Aurik - __BACK_URL = "http://cloud-3.steamusercontent.com/ugc/948455238665576576/85063172B8C340602E8D6C783A457122F53F7843/" - - def __init__(self, card_queue, resolution, language): + def __init__(self, url_queue, resolution, language): threading.Thread.__init__(self) - self.__queue = card_queue + self.__queue = url_queue self.__resolution = resolution self.__language = language self.__images = {} @@ -26,14 +20,14 @@ class ImageLoader(threading.Thread): logger = logging.getLogger(__name__) while not self.__queue.empty(): - # take next card - card = self.__queue.get() + # take next url + url = self.__queue.get() - # fetch card image (retry on fail) + # fetch image (retry on fail) while True: - logger.info(f"get image for card {card}") + logger.info(f"downloading image {url}") try: - res = requests.get(ImageLoader.__FACE_URL.format(card.code, self.__language)) + res = requests.get(url) image = Image.open(io.BytesIO(res.content)) # unify images @@ -44,31 +38,32 @@ class ImageLoader(threading.Thread): pass # put image in correct position - self.__images[card] = image + self.__images[url] = image # image is processed self.__queue.task_done() @classmethod - def load(cls, cards, resolution, language, num_threads): - card_queue = queue.Queue() - for card in cards: - card_queue.put(card) + def load(cls, urls, resolution, language, num_threads): + url_queue = queue.Queue() + for url in urls: + url_queue.put(url) loaders = [] for _ in range(num_threads): - loader = cls(card_queue, resolution, language) + loader = cls(url_queue, resolution, language) loaders.append(loader) loader.start() - card_queue.join() + url_queue.join() + # stitch all "images" dicts together images = {} for loader in loaders: images = {**images, **loader.images} # sort images to match the initial "cards" list - images = [images[card] for card in cards] + images = [images[url] for url in urls] return images diff --git a/fftcg/opus.py b/fftcg/opus.py index 68bae21..e98dd7b 100644 --- a/fftcg/opus.py +++ b/fftcg/opus.py @@ -6,33 +6,37 @@ from .cards import Cards class Opus(Cards): - def __init__(self, number): + def __init__(self, opus_id): logger = logging.getLogger(__name__) - if isinstance(number, str) and number.isnumeric(): - set_name = f"Opus {roman.toRoman(int(number))}" - number = str(number) + if isinstance(opus_id, str) and opus_id.isnumeric(): + roman_opus_id = roman.toRoman(int(opus_id)) + api_set = f"Opus {roman_opus_id.upper()}" + self.__number = str(opus_id) + self.__name = f"opus_{opus_id}" - elif number == "Boss Deck Chaos": - set_name = number - number = "B" + elif opus_id == "chaos": + api_set = "Boss Deck Chaos" + self.__number = "B" + self.__name = "boss_deck_chaos" else: - set_name = "?" - number = "?" + api_set = "?" + self.__number = "?" + self.__name = "?" params = { "text": "", - # "element": ["fire"], - "set": [set_name], + "element": ["darkness"], + "set": [api_set], } Cards.__init__(self, params) - # filter out reprints - reprints = [card for card in self if not card.code.startswith(number)] - for reprint in reprints: - self.remove(reprint) + # remove reprints + for card in self: + if not card.code.startswith(self.__number + "-"): + self.remove(card) # sort every element alphabetically self.sort(key=lambda x: x.code) @@ -41,3 +45,11 @@ class Opus(Cards): for card in self: logger.info(f"imported card {card}") + + @property + def name(self): + return self.__name + + @property + def number(self): + return self.__number diff --git a/main.py b/main.py index 78474b9..a9a12d0 100755 --- a/main.py +++ b/main.py @@ -3,18 +3,22 @@ import argparse import logging import os -from fftcg.opus import Opus from fftcg.book import Book +from fftcg.opus import Opus + +# constants +GRID = 7, 10 # default in TTsim: 7 rows, 10 columns +RESOLUTION = 429, 600 # default in TTsim: 480x670 pixels per card def main(): - # Setup CLI + # set up CLI parser = argparse.ArgumentParser( description='Imports FFTCG cards for TT-Sim.') parser.add_argument( - 'opusid', - default="2", + 'opus_id', + default="1", metavar="OpusID", nargs="?", help='the Opus to import') @@ -28,18 +32,22 @@ def main(): args = parser.parse_args() - # Setup logging + # set up logging logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(threadName)s %(message)s') - opus = Opus(args.opusid) - # output directory if not os.path.exists("out"): os.mkdir("out") os.chdir("out") - book = Book(opus, (7, 10), (429, 600), "eg", 16) - book.save(f"opus_{args.opusid}_{{}}.jpg") + # main program + opus = Opus(args.opus_id) + book = Book(opus, GRID, RESOLUTION, "eg", 16) + book.save(f"{opus.name}_{{}}.jpg") + + # bye + logging.info("Done. Put the generated JSON files in your 'Saved Objects' Folder.") + logging.info("Thanks for using fftcgtool!") if __name__ == '__main__':