mirror of
https://github.com/ldericher/fftcgtool
synced 2025-01-15 15:02:59 +00:00
composition works
This commit is contained in:
parent
cb4031b422
commit
f375d5792d
5 changed files with 129 additions and 45 deletions
|
@ -1,25 +1,43 @@
|
||||||
import queue
|
import logging
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from .imageloader import ImageLoader
|
||||||
|
|
||||||
|
|
||||||
|
def chunks(whole: list, chunk_size):
|
||||||
|
# while there are elements
|
||||||
|
while whole:
|
||||||
|
# get a chunk
|
||||||
|
yield whole[:chunk_size]
|
||||||
|
# remove that chunk
|
||||||
|
whole = whole[chunk_size:]
|
||||||
|
|
||||||
|
|
||||||
class Book:
|
class Book:
|
||||||
def __init__(self, cards):
|
def __init__(self, cards, grid, resolution, language, num_threads):
|
||||||
self.__cards = cards
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def __get_pages(self, grid):
|
images = ImageLoader.load(cards, resolution, language, num_threads)
|
||||||
# cards per sheet
|
images = [images[card] for card in cards]
|
||||||
|
|
||||||
|
# shorthands
|
||||||
|
# rows and columns per sheet
|
||||||
r, c = grid
|
r, c = grid
|
||||||
capacity = r * c - 1
|
# width, height per card
|
||||||
# flat copy
|
w, h = resolution
|
||||||
cards = self.__cards
|
|
||||||
|
|
||||||
# while there are cards
|
self.__pages = []
|
||||||
while cards:
|
for images in chunks(images, r * c - 1):
|
||||||
# get a chunk
|
page = Image.new("RGB", (c * w, r * h))
|
||||||
yield cards[:capacity]
|
logger.info(f"New image: {page.size[0]}x{page.size[1]}")
|
||||||
# remove that chunk
|
|
||||||
cards = cards[capacity:]
|
|
||||||
|
|
||||||
def populate(self, grid, resolution, threadnum=16):
|
for i, image in enumerate(images):
|
||||||
card_queue = queue.Queue()
|
x, y = (i % c) * w, (i // c) * h
|
||||||
for i, card in enumerate(self.__cards):
|
page.paste(image, (x, y, x + w, y + h))
|
||||||
card_queue.put((i, card))
|
|
||||||
|
self.__pages.append(page)
|
||||||
|
|
||||||
|
def save(self, filename):
|
||||||
|
for i, page in enumerate(self.__pages):
|
||||||
|
page.save(filename.format(i))
|
||||||
|
|
|
@ -49,17 +49,21 @@ class Card:
|
||||||
self.__text = data[f"Text_{language}"]
|
self.__text = data[f"Text_{language}"]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"'{self.__name}' ({'/'.join(self.__elements)}, {self.get_code()})"
|
return f"'{self.__name}' ({'/'.join(self.__elements)}, {self.code})"
|
||||||
|
|
||||||
# 6-048C
|
# 6-048C
|
||||||
def get_code(self):
|
@property
|
||||||
|
def code(self):
|
||||||
return f"{self.__opus}-{self.__serial}{self.__rarity}"
|
return f"{self.__opus}-{self.__serial}{self.__rarity}"
|
||||||
|
|
||||||
def get_name(self):
|
@property
|
||||||
|
def name(self):
|
||||||
return self.__name
|
return self.__name
|
||||||
|
|
||||||
def get_text(self):
|
@property
|
||||||
|
def text(self):
|
||||||
return self.__text
|
return self.__text
|
||||||
|
|
||||||
def get_elements(self):
|
@property
|
||||||
|
def elements(self):
|
||||||
return self.__elements
|
return self.__elements
|
||||||
|
|
|
@ -24,15 +24,16 @@ class ImageLoader(threading.Thread):
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
while not self.__queue.empty():
|
while not self.__queue.empty():
|
||||||
# take next card
|
# take next card
|
||||||
i, card = self.__queue.get()
|
card = self.__queue.get()
|
||||||
|
|
||||||
# fetch card image (retry on fail)
|
# fetch card image (retry on fail)
|
||||||
while True:
|
while True:
|
||||||
logger.info("get image for card {}".format(card))
|
logger.info(f"get image for card {card}")
|
||||||
try:
|
try:
|
||||||
res = requests.get(ImageLoader.__FACE_URL.format(card.get_code(), self.__language))
|
res = requests.get(ImageLoader.__FACE_URL.format(card.code, self.__language))
|
||||||
image = Image.open(io.BytesIO(res.content))
|
image = Image.open(io.BytesIO(res.content))
|
||||||
image.convert("RGB")
|
image.convert("RGB")
|
||||||
image = image.resize(self.__resolution, Image.BICUBIC)
|
image = image.resize(self.__resolution, Image.BICUBIC)
|
||||||
|
@ -41,21 +42,31 @@ class ImageLoader(threading.Thread):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# put image in correct position
|
# put image in correct position
|
||||||
self.__images[i] = image
|
self.__images[card] = image
|
||||||
|
|
||||||
# image is processed
|
# image is processed
|
||||||
self.__queue.task_done()
|
self.__queue.task_done()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def spawn(cls, cards, resolution, language="eg", num_threads=16):
|
def load(cls, cards, resolution, language, num_threads):
|
||||||
card_queue = queue.Queue()
|
card_queue = queue.Queue()
|
||||||
for i, card in enumerate(cards):
|
for card in cards:
|
||||||
card_queue.put((i, card))
|
card_queue.put(card)
|
||||||
|
|
||||||
|
loaders = []
|
||||||
for _ in range(num_threads):
|
for _ in range(num_threads):
|
||||||
cls(card_queue, resolution, language).start()
|
loader = cls(card_queue, resolution, language)
|
||||||
|
loaders.append(loader)
|
||||||
|
loader.start()
|
||||||
|
|
||||||
return card_queue
|
card_queue.join()
|
||||||
|
|
||||||
def get_images(self):
|
images = {}
|
||||||
|
for loader in loaders:
|
||||||
|
images = {**images, **loader.images}
|
||||||
|
|
||||||
|
return images
|
||||||
|
|
||||||
|
@property
|
||||||
|
def images(self):
|
||||||
return self.__images
|
return self.__images
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
import roman
|
import roman
|
||||||
|
|
||||||
from .cards import Cards
|
from .cards import Cards
|
||||||
|
@ -5,18 +7,37 @@ from .cards import Cards
|
||||||
|
|
||||||
class Opus(Cards):
|
class Opus(Cards):
|
||||||
def __init__(self, number):
|
def __init__(self, number):
|
||||||
if isinstance(number, int):
|
logger = logging.getLogger(__name__)
|
||||||
number = f"Opus {roman.toRoman(number)}"
|
|
||||||
|
if isinstance(number, str) and number.isnumeric():
|
||||||
|
set_name = f"Opus {roman.toRoman(int(number))}"
|
||||||
|
number = str(number)
|
||||||
|
|
||||||
|
elif number == "Boss Deck Chaos":
|
||||||
|
set_name = number
|
||||||
|
number = "B"
|
||||||
|
|
||||||
|
else:
|
||||||
|
set_name = "?"
|
||||||
|
number = "?"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"text": "",
|
"text": "",
|
||||||
"element": ["fire"],
|
# "element": ["fire"],
|
||||||
"set": [number],
|
"set": [set_name],
|
||||||
}
|
}
|
||||||
|
|
||||||
Cards.__init__(self, params)
|
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)
|
||||||
|
|
||||||
# sort every element alphabetically
|
# sort every element alphabetically
|
||||||
self.sort(key=lambda x: x.get_code())
|
self.sort(key=lambda x: x.code)
|
||||||
self.sort(key=lambda x: x.get_name())
|
self.sort(key=lambda x: x.name)
|
||||||
self.sort(key=lambda x: "Multi" if len(x.get_elements()) > 1 else x.get_elements()[0])
|
self.sort(key=lambda x: "Multi" if len(x.elements) > 1 else x.elements[0])
|
||||||
|
|
||||||
|
for card in self:
|
||||||
|
logger.info(f"imported card {card}")
|
||||||
|
|
40
main.py
40
main.py
|
@ -1,15 +1,45 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
from fftcg.imageloader import ImageLoader
|
|
||||||
from fftcg.opus import Opus
|
from fftcg.opus import Opus
|
||||||
|
from fftcg.book import Book
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
opus = Opus(14)
|
# Setup CLI
|
||||||
print(opus)
|
parser = argparse.ArgumentParser(
|
||||||
|
description='Imports FFTCG cards for TT-Sim.')
|
||||||
|
|
||||||
queue = ImageLoader.spawn(opus, (429, 600))
|
parser.add_argument(
|
||||||
queue.join()
|
'opusid',
|
||||||
|
default="2",
|
||||||
|
metavar="OpusID",
|
||||||
|
nargs="?",
|
||||||
|
help='the Opus to import')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-n', '--num_threads',
|
||||||
|
type=int,
|
||||||
|
default=20,
|
||||||
|
metavar="COUNT",
|
||||||
|
help='maximum number of concurrent requests')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Setup 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")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
Loading…
Reference in a new issue