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

Begin rewrite

This commit is contained in:
Jörn-Michael Miehe 2021-08-03 01:37:22 +02:00
parent cc5955865b
commit cd115e2f9e
11 changed files with 124 additions and 257 deletions

3
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

8
.idea/fftcgtool_v2.iml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Pipenv (fftcgtool_v2)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fftcgtool_v2.iml" filepath="$PROJECT_DIR$/.idea/fftcgtool_v2.iml" />
</modules>
</component>
</project>

12
Pipfile Normal file
View file

@ -0,0 +1,12 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
[packages]
requests = "*"
[requires]
python_version = "3.6"

60
Pipfile.lock generated Normal file
View file

@ -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": {}
}

89
card.py
View file

@ -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

0
fftcg/__init__.py Normal file
View file

23
fftcg/card.py Normal file
View file

@ -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}"

168
opus.py
View file

@ -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)