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:
parent
28293118da
commit
a359c1df4c
4 changed files with 88 additions and 49 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
26
main.py
|
@ -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__':
|
||||||
|
|
Loading…
Reference in a new issue