From cd115e2f9e588c366c8ac3e555d8d425360da4b6 Mon Sep 17 00:00:00 2001 From: LDericher <40151420+ldericher@users.noreply.github.com> Date: Tue, 3 Aug 2021 01:37:22 +0200 Subject: [PATCH] Begin rewrite --- .idea/.gitignore | 3 + .idea/fftcgtool_v2.iml | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + Pipfile | 12 ++ Pipfile.lock | 60 +++++++ card.py | 89 ---------- fftcg/__init__.py | 0 fftcg/card.py | 23 +++ opus.py | 168 ------------------ 11 files changed, 124 insertions(+), 257 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/fftcgtool_v2.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 card.py create mode 100644 fftcg/__init__.py create mode 100644 fftcg/card.py delete mode 100644 opus.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/fftcgtool_v2.iml b/.idea/fftcgtool_v2.iml new file mode 100644 index 0000000..d0876a7 --- /dev/null +++ b/.idea/fftcgtool_v2.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..dcdd1c9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..92ac61d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..90617ca --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +requests = "*" + +[requires] +python_version = "3.6" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..f7d0e73 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,60 @@ +{ + "_meta": { + "hash": { + "sha256": "8739d581819011fea34feca8cc077062d6bdfee39c7b37a8ed48c5e0a8b14837" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" + ], + "version": "==2021.5.30" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", + "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" + ], + "markers": "python_version >= '3'", + "version": "==2.0.4" + }, + "idna": { + "hashes": [ + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + ], + "markers": "python_version >= '3'", + "version": "==3.2" + }, + "requests": { + "hashes": [ + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + ], + "index": "pypi", + "version": "==2.26.0" + }, + "urllib3": { + "hashes": [ + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.6" + } + }, + "develop": {} +} diff --git a/card.py b/card.py deleted file mode 100644 index 10de774..0000000 --- a/card.py +++ /dev/null @@ -1,89 +0,0 @@ -import requests - -from PIL import Image -from io import BytesIO - -# Card front by Square API -FACEURL = "https://fftcg.cdn.sewest.net/images/cards/full/{}_eg.jpg" - -# Card back image by Aurik -BACKURL = "http://cloud-3.steamusercontent.com/ugc/948455238665576576/85063172B8C340602E8D6C783A457122F53F7843/" - -class Card: - def __init__(self, data): - # check if this is a card back - if data == 0: - self._serial = "0-000" - self._name = "[cardback]" - self._rarity = "X" - self._element = "None" - self._elements = None - - self._description = "None" - self._iurl = BACKURL - - # else import from data - else: - self._serial = data["serial_number"] - self._name = data["name"] - self._rarity = data["rarity"][0] - self._element = data["elements"][0] - - # handle multi element cards - if len(data["elements"]) > 1: - self._element = "Multi" - self._elements = data["elements"] - - # fix db typos - if self._serial == "12-052": - self._rarity = "H" - elif self._serial == "12-049": - self._rarity = "H" - - self._description = "\n\n".join(data["abilities"]) - self._iurl = FACEURL.format(self.get_id()) # official url - #self._iurl = data["image"] # ffdecks url - - # 'Shinra' (Wind, 6-048C) - def __str__(self): - return "'{}' ({}, {})".format(self._name, self._element, self.get_id()) - - # 6-048C - def get_id(self): - rarity = self._rarity if self._rarity in ["C", "R", "H", "L", "S"] else "" - return "{}{}".format(self._serial, rarity) - - # return in dictionary format - def get_dict(self): - return { - "Nickname": self._name, - "Description": self._description, - - "Name": "Card", - "Transform": { - "scaleX": 2.17822933, - "scaleY": 1.0, - "scaleZ": 2.17822933 - }, - "Locked": False, - "Grid": True, - "Snap": True, - "Autoraise": True, - "Sticky": True, - "Tooltip": True, - "GridProjection": False, - "SidewaysCard": False, - "Hands": True - } - - # download and resize card image - def get_image(self, resolution): - try: - response = requests.get(self._iurl) - except: - return False - - im = Image.open(BytesIO(response.content)) - im = im.convert("RGB") - im = im.resize(resolution, Image.BICUBIC) - return im diff --git a/fftcg/__init__.py b/fftcg/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fftcg/card.py b/fftcg/card.py new file mode 100644 index 0000000..94f327a --- /dev/null +++ b/fftcg/card.py @@ -0,0 +1,23 @@ +import re + + +class Card: + def __init__(self, data): + code_match = re.match(r'([0-9]+)-([0-9]+)([CRHLSB])', data["Code"]) + if code_match: + self._opus, self._serial, self._rarity = code_match.groups() + + else: + self._opus = "PR" + self._serial = re.match(r'PR-([0-9]+)', data["Code"]).group(1) + self._rarity = "P" + + self._name = data["Name_EN"] + self._element = data["Element"] + + def __str__(self): + return f"'{self._name}' ({self._element}, {self.get_id()})" + + # 6-048C + def get_id(self): + return f"{self._opus}-{self._serial}{self._rarity}" diff --git a/opus.py b/opus.py deleted file mode 100644 index 5d3e7a7..0000000 --- a/opus.py +++ /dev/null @@ -1,168 +0,0 @@ -import queue -import threading -import logging -import json - -from card import Card, BACKURL -from PIL import Image - - -# multithreaded card image loading -class imageLoader(threading.Thread): - def __init__(self, queue, composite, composite_lock, grid, resolution): - threading.Thread.__init__(self) - self.__queue = queue - self.__composite = composite - self.__lock = composite_lock - self.__grid = grid - self.__resolution = resolution - - def run(self): - logger = logging.getLogger(__name__) - # shorthands - r, c = self.__grid # rows and columns per sheet - w, h = self.__resolution # width, height per card - - while not self.__queue.empty(): - # take next card - i, card = self.__queue.get() - - # fetch card image (retry on fail) - while True: - logger.info("get image for card {}".format(card)) - im = card.get_image(self.__resolution) - if im: - break - - # paste image in correct position - self.__lock.acquire() - x, y = (i % c) * w, (i // c) * h - logger.info("paste image {} at P{}({}, {})".format(im.mode, i, x, y)) - self.__composite.paste(im, (x, y, x+w, y+h)) - self.__lock.release() - - # image is processed - self.__queue.task_done() - - -class Opus: - def __init__(self, data): - logger = logging.getLogger(__name__) - self._cards = [] - - for card_data in data: - card = Card(card_data) - logger.info("Imported card {}".format(card)) - self._cards.append(card) - - # sort every element alphabetically - self._cards.sort(key=lambda x: x._serial) - self._cards.sort(key=lambda x: x._name) - self._cards.sort(key=lambda x: x._element) - - def __get_sheets(self, grid): - # cards per sheet - count = grid[0]*grid[1] - 1 - # flat copy - cards = self._cards - - # while there are cards - while cards: - # get a chunk - yield cards[:count] - # remove that chunk - cards = cards[count:] - - def get_images(self, grid, resolution, threadnum=16): - logger = logging.getLogger(__name__) - # shorthands - r, c = grid # rows and columns per sheet - w, h = resolution # width, height per card - - for sheet in self.__get_sheets(grid): - # enqueue a sheet of cards - cardQueue = queue.Queue() - for i, card in enumerate(sheet): - cardQueue.put((i, card)) - - # add hidden face - hidden = Card(0) - cardQueue.put((r * c - 1, hidden)) - - # create a new card sheet - sheet = Image.new("RGB", (c*w, r*h)) - logger.info("New image: {}x{}".format(*sheet.size)) - - # beware concurrent paste - sheet_lock = threading.Lock() - - # start multithreading, wait for finish - for _ in range(threadnum): - imageLoader(cardQueue, sheet, sheet_lock, grid, resolution).start() - cardQueue.join() - - # sheet image is generated, return now - yield sheet - - def get_json(self, opusid, deckname, grid, cardfilter, faceurls): - # shorthands - r, c = grid # rows and columns per sheet - - jsondict = { "ObjectStates": [ { - "Name": "Deck", - "Nickname": "Opus {} {}".format(opusid, deckname), - "Description": "", - - "Transform": { - "scaleX": 2.17822933, - "scaleY": 1.0, - "scaleZ": 2.17822933 - }, - - "Locked": False, - "Grid": True, - "Snap": True, - "Autoraise": True, - "Sticky": True, - "Tooltip": True, - "GridProjection": False, - "Hands": False, - "SidewaysCard": False, - - "DeckIDs": [], - "CustomDeck": {}, - "ContainedObjects": [] - } ] } - - for sheetnum, sheet in enumerate(self.__get_sheets(grid)): - # get current face - faceurl = faceurls[sheetnum] - # first sheet is "CustomDeck 1" - sheetnum = sheetnum + 1 - - # recurring sheet dictionary - sheetdict = { str(sheetnum): { - "FaceURL": faceurl, - "BackURL": BACKURL, - "NumWidth": c, - "NumHeight": r - } } - - for cardnum, card in enumerate(sheet): - if not cardfilter(card): - continue - - # cardid 123 is on "CustomDeck 1", 3rd row, 4th column - cardid = sheetnum * 100 + (cardnum // c) * 10 + (cardnum % c) * 1 - jsondict["ObjectStates"][0]["DeckIDs"].append(cardid) - - # Add card object to ContainedObjects - carddict = card.get_dict() - carddict["CardID"] = cardid - carddict["CustomDeck"] = sheetdict - jsondict["ObjectStates"][0]["ContainedObjects"].append(carddict) - - # Add sheet to CustomDecks - jsondict["ObjectStates"][0]["CustomDeck"].update(sheetdict) - - return json.dumps(jsondict, indent=2)