mirror of
https://github.com/ldericher/fftcgtool
synced 2025-01-15 15:02:59 +00:00
Begin rewrite
This commit is contained in:
parent
cc5955865b
commit
cd115e2f9e
11 changed files with 124 additions and 257 deletions
3
.idea/.gitignore
vendored
Normal file
3
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
8
.idea/fftcgtool_v2.iml
Normal file
8
.idea/fftcgtool_v2.iml
Normal 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>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal 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
4
.idea/misc.xml
Normal 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
8
.idea/modules.xml
Normal 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
12
Pipfile
Normal 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
60
Pipfile.lock
generated
Normal 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
89
card.py
|
@ -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
0
fftcg/__init__.py
Normal file
23
fftcg/card.py
Normal file
23
fftcg/card.py
Normal 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
168
opus.py
|
@ -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)
|
Loading…
Reference in a new issue