1
0
Fork 0
mirror of https://github.com/ldericher/fftcgtool synced 2025-01-15 15:02:59 +00:00

url based imageloader.py, composition with card backs

This commit is contained in:
Jörn-Michael Miehe 2021-08-05 00:17:47 +02:00
parent 28293118da
commit a359c1df4c
4 changed files with 88 additions and 49 deletions

View file

@ -15,25 +15,49 @@ def chunks(whole: list, chunk_size):
class Book: 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): def __init__(self, cards, grid, resolution, language, num_threads):
logger = logging.getLogger(__name__) 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 # shorthands
# rows and columns per sheet # rows and columns per sheet
r, c = grid r, c = grid
# capacity of grid (reserve last space for card back)
grid_capacity = r * c - 1
# width, height per card # width, height per card
w, h = resolution w, h = resolution
def paste_image(page, index, image):
x, y = (index % c) * w, (index // c) * h
page.paste(image, (x, y))
self.__pages = [] 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)) page = Image.new("RGB", (c * w, r * h))
logger.info(f"New image: {page.size[0]}x{page.size[1]}") logger.info(f"New image: {page.size[0]}x{page.size[1]}")
# paste card faces onto page
for i, image in enumerate(images): for i, image in enumerate(images):
x, y = (i % c) * w, (i // c) * h paste_image(page, i, image)
page.paste(image, (x, y))
# paste card back in last position
paste_image(page, c * r - 1, back_image)
self.__pages.append(page) self.__pages.append(page)

View file

@ -8,16 +8,10 @@ from PIL import Image
class ImageLoader(threading.Thread): class ImageLoader(threading.Thread):
# Card faces by Square API def __init__(self, url_queue, resolution, language):
__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):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.__queue = card_queue self.__queue = url_queue
self.__resolution = resolution self.__resolution = resolution
self.__language = language self.__language = language
self.__images = {} self.__images = {}
@ -26,14 +20,14 @@ class ImageLoader(threading.Thread):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
while not self.__queue.empty(): while not self.__queue.empty():
# take next card # take next url
card = self.__queue.get() url = self.__queue.get()
# fetch card image (retry on fail) # fetch image (retry on fail)
while True: while True:
logger.info(f"get image for card {card}") logger.info(f"downloading image {url}")
try: try:
res = requests.get(ImageLoader.__FACE_URL.format(card.code, self.__language)) res = requests.get(url)
image = Image.open(io.BytesIO(res.content)) image = Image.open(io.BytesIO(res.content))
# unify images # unify images
@ -44,31 +38,32 @@ class ImageLoader(threading.Thread):
pass pass
# put image in correct position # put image in correct position
self.__images[card] = image self.__images[url] = image
# image is processed # image is processed
self.__queue.task_done() self.__queue.task_done()
@classmethod @classmethod
def load(cls, cards, resolution, language, num_threads): def load(cls, urls, resolution, language, num_threads):
card_queue = queue.Queue() url_queue = queue.Queue()
for card in cards: for url in urls:
card_queue.put(card) url_queue.put(url)
loaders = [] loaders = []
for _ in range(num_threads): for _ in range(num_threads):
loader = cls(card_queue, resolution, language) loader = cls(url_queue, resolution, language)
loaders.append(loader) loaders.append(loader)
loader.start() loader.start()
card_queue.join() url_queue.join()
# stitch all "images" dicts together
images = {} images = {}
for loader in loaders: for loader in loaders:
images = {**images, **loader.images} images = {**images, **loader.images}
# sort images to match the initial "cards" list # sort images to match the initial "cards" list
images = [images[card] for card in cards] images = [images[url] for url in urls]
return images return images

View file

@ -6,33 +6,37 @@ from .cards import Cards
class Opus(Cards): class Opus(Cards):
def __init__(self, number): def __init__(self, opus_id):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if isinstance(number, str) and number.isnumeric(): if isinstance(opus_id, str) and opus_id.isnumeric():
set_name = f"Opus {roman.toRoman(int(number))}" roman_opus_id = roman.toRoman(int(opus_id))
number = str(number) api_set = f"Opus {roman_opus_id.upper()}"
self.__number = str(opus_id)
self.__name = f"opus_{opus_id}"
elif number == "Boss Deck Chaos": elif opus_id == "chaos":
set_name = number api_set = "Boss Deck Chaos"
number = "B" self.__number = "B"
self.__name = "boss_deck_chaos"
else: else:
set_name = "?" api_set = "?"
number = "?" self.__number = "?"
self.__name = "?"
params = { params = {
"text": "", "text": "",
# "element": ["fire"], "element": ["darkness"],
"set": [set_name], "set": [api_set],
} }
Cards.__init__(self, params) Cards.__init__(self, params)
# filter out reprints # remove reprints
reprints = [card for card in self if not card.code.startswith(number)] for card in self:
for reprint in reprints: if not card.code.startswith(self.__number + "-"):
self.remove(reprint) self.remove(card)
# sort every element alphabetically # sort every element alphabetically
self.sort(key=lambda x: x.code) self.sort(key=lambda x: x.code)
@ -41,3 +45,11 @@ class Opus(Cards):
for card in self: for card in self:
logger.info(f"imported card {card}") logger.info(f"imported card {card}")
@property
def name(self):
return self.__name
@property
def number(self):
return self.__number

26
main.py
View file

@ -3,18 +3,22 @@ import argparse
import logging import logging
import os import os
from fftcg.opus import Opus
from fftcg.book import Book 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(): def main():
# Setup CLI # set up CLI
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Imports FFTCG cards for TT-Sim.') description='Imports FFTCG cards for TT-Sim.')
parser.add_argument( parser.add_argument(
'opusid', 'opus_id',
default="2", default="1",
metavar="OpusID", metavar="OpusID",
nargs="?", nargs="?",
help='the Opus to import') help='the Opus to import')
@ -28,18 +32,22 @@ def main():
args = parser.parse_args() args = parser.parse_args()
# Setup logging # set up logging
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(threadName)s %(message)s') logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(threadName)s %(message)s')
opus = Opus(args.opusid)
# output directory # output directory
if not os.path.exists("out"): if not os.path.exists("out"):
os.mkdir("out") os.mkdir("out")
os.chdir("out") os.chdir("out")
book = Book(opus, (7, 10), (429, 600), "eg", 16) # main program
book.save(f"opus_{args.opusid}_{{}}.jpg") 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__': if __name__ == '__main__':