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)