improved data loading
This commit is contained in:
+6
-3
@@ -14,9 +14,12 @@ dst_dir = utils.relative_path(__file__, "templates")
|
|||||||
|
|
||||||
templates_dir = utils.relative_path(__file__, "..", "templates")
|
templates_dir = utils.relative_path(__file__, "..", "templates")
|
||||||
|
|
||||||
for f in os.listdir(dst_dir):
|
if path.exists(dst_dir):
|
||||||
if path.isfile(path.join(dst_dir, f)):
|
for f in os.listdir(dst_dir):
|
||||||
os.unlink(path.join(dst_dir, f))
|
if path.isfile(path.join(dst_dir, f)):
|
||||||
|
os.unlink(path.join(dst_dir, f))
|
||||||
|
else:
|
||||||
|
os.mkdir(dst_dir)
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ import logging
|
|||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
DEFAULT_FONT = "arial"
|
|
||||||
DEFAULT_FONT_SIZE = 0.05
|
|
||||||
|
|
||||||
FONT_DIR = utils.relative_path(__file__, "..", "fonts")
|
FONT_DIR = utils.relative_path(__file__, "..", "fonts")
|
||||||
TEMPLATES_DIR = utils.relative_path(__file__, "..", "templates")
|
TEMPLATES_DIR = utils.relative_path(__file__, "..", "templates")
|
||||||
|
|
||||||
@@ -65,8 +62,7 @@ def draw_text(draw, size, text, debug=False):
|
|||||||
# TODO rotation
|
# TODO rotation
|
||||||
# https://stackoverflow.com/questions/245447/how-do-i-draw-text-at-an-angle-using-pythons-pil
|
# https://stackoverflow.com/questions/245447/how-do-i-draw-text-at-an-angle-using-pythons-pil
|
||||||
if text.text is not None and len(text.text.strip()) > 0:
|
if text.text is not None and len(text.text.strip()) > 0:
|
||||||
if text.font is None:
|
text.init() # load default values
|
||||||
text.font = DEFAULT_FONT
|
|
||||||
if text.font in FONTS:
|
if text.font in FONTS:
|
||||||
text.text, font = fit_text(size, text)
|
text.text, font = fit_text(size, text)
|
||||||
draw.text(get_pos(size, text, font), text.text, fill=text.fill, align=text.align, font=font,
|
draw.text(get_pos(size, text, font), text.text, fill=text.fill, align=text.align, font=font,
|
||||||
@@ -90,8 +86,6 @@ def fit_text(size, text):
|
|||||||
max_width = round(size[0] * (text.x_range[1] - text.x_range[0]))
|
max_width = round(size[0] * (text.x_range[1] - text.x_range[0]))
|
||||||
max_height = round(size[1] * (text.y_range[1] - text.y_range[0]))
|
max_height = round(size[1] * (text.y_range[1] - text.y_range[0]))
|
||||||
text_size = None
|
text_size = None
|
||||||
if text.font_size is None:
|
|
||||||
text.font_size = DEFAULT_FONT_SIZE
|
|
||||||
font_size = round(text.font_size * min(size)) + 1
|
font_size = round(text.font_size * min(size)) + 1
|
||||||
font = FONTS[text.font]
|
font = FONTS[text.font]
|
||||||
t = ""
|
t = ""
|
||||||
@@ -127,8 +121,6 @@ def get_pos(size, text, font):
|
|||||||
max_x = round(text.x_range[1] * size[0])
|
max_x = round(text.x_range[1] * size[0])
|
||||||
min_y = round(text.y_range[0] * size[1])
|
min_y = round(text.y_range[0] * size[1])
|
||||||
max_y = round(text.y_range[1] * size[1])
|
max_y = round(text.y_range[1] * size[1])
|
||||||
pos_x = 0
|
|
||||||
pos_y = 0
|
|
||||||
text_size = font.getsize_multiline(text.text, stroke_width=text.stroke_width * font.size)
|
text_size = font.getsize_multiline(text.text, stroke_width=text.stroke_width * font.size)
|
||||||
|
|
||||||
if int(text.position.value) // 3 == 0:
|
if int(text.position.value) // 3 == 0:
|
||||||
|
|||||||
+32
-73
@@ -1,6 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os.path as path
|
|
||||||
|
|
||||||
from .types import Pos, Text, Meme
|
from .types import Pos, Text, Meme
|
||||||
from . import utils
|
from . import utils
|
||||||
@@ -44,63 +43,44 @@ def load_item(i, item):
|
|||||||
try:
|
try:
|
||||||
if not (isinstance(item, dict)):
|
if not (isinstance(item, dict)):
|
||||||
raise TypeError(f"root is not a dict")
|
raise TypeError(f"root is not a dict")
|
||||||
item_id = utils.read_key(item, "id")
|
item_id = utils.read_key(item, "id", types=[str])
|
||||||
if item_id in DATA:
|
if item_id in DATA:
|
||||||
raise NameError(f"id '{item_id}' already existing")
|
raise NameError(f"id '{item_id}' already existing")
|
||||||
based_on = utils.read_key_safe(item, "based_on")
|
based_on = utils.read_key_safe(item, "based_on", types=[str])
|
||||||
abstract = utils.read_key_safe(item, "abstract", False)
|
|
||||||
aliases = utils.read_key_safe(item, "aliases", [])
|
|
||||||
if not utils.is_list_of(aliases, [str]):
|
|
||||||
raise TypeError(f"'aliases' is not a list of str")
|
|
||||||
template = None
|
|
||||||
font = None
|
|
||||||
font_size = None
|
|
||||||
texts = None
|
|
||||||
if based_on is not None:
|
if based_on is not None:
|
||||||
if based_on in DATA:
|
if based_on in DATA:
|
||||||
template = DATA[based_on].template
|
meme = DATA[based_on].clone()
|
||||||
font = DATA[based_on].font
|
meme.id = item_id
|
||||||
font_size = DATA[based_on].font_size
|
|
||||||
texts = DATA[based_on].clone_texts()
|
|
||||||
else:
|
else:
|
||||||
raise NameError(f"Reference '{based_on}' not found in data, make sur it's placed before this one")
|
raise NameError(f"Reference '{based_on}' not found in data, make sur it's placed before this one")
|
||||||
if not abstract:
|
else:
|
||||||
template = utils.read_key(item, "template", template)
|
meme = Meme(item_id)
|
||||||
font = utils.read_key_safe(item, "font", font)
|
meme.abstract = utils.read_key_safe(item, "abstract", False, types=[bool])
|
||||||
font_size = utils.read_key_safe(item, "font_size", font_size)
|
meme.aliases = utils.read_key_safe(item, "aliases", [], types=[str], is_list=True)
|
||||||
raw_texts = utils.read_key(item, "texts", texts)
|
meme.text_base = load_text(0, item, meme.text_base)
|
||||||
if texts is None:
|
if not meme.abstract:
|
||||||
if not (isinstance(raw_texts, list)):
|
meme.template = utils.read_key(item, "template", meme.template, types=[str])
|
||||||
raise TypeError(f"'texts' is not a list")
|
raw_texts = utils.read_key(item, "texts", meme.texts, types=[dict], is_list=True)
|
||||||
texts = []
|
if meme.texts is None:
|
||||||
|
meme.texts = []
|
||||||
for j in range(len(raw_texts)):
|
for j in range(len(raw_texts)):
|
||||||
raw_text = raw_texts[j]
|
raw_text = raw_texts[j]
|
||||||
try:
|
try:
|
||||||
texts += [load_text(j, raw_text)]
|
meme.texts += [load_text(j, raw_text)]
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
logger.warning(f"Item '{item_id}'({i}) / Text {j}: {e}")
|
logger.warning(f"Item '{item_id}'({i}) / Text {j}: {e}")
|
||||||
if font is not None:
|
for text in meme.texts:
|
||||||
if not (isinstance(font, str)):
|
text.update(meme.text_base)
|
||||||
raise TypeError(f"'font' is not a str")
|
if len(meme.texts) == 0:
|
||||||
for text in texts:
|
|
||||||
if text.font is None:
|
|
||||||
text.font = font
|
|
||||||
if font_size is not None:
|
|
||||||
if not (isinstance(font_size, float)):
|
|
||||||
raise TypeError(f"'font_size' is not a float")
|
|
||||||
for text in texts:
|
|
||||||
if text.font_size is None:
|
|
||||||
text.font_size = font_size
|
|
||||||
if len(texts) == 0:
|
|
||||||
logger.warning(f"Item '{item_id}'({i}): no texts loaded")
|
logger.warning(f"Item '{item_id}'({i}): no texts loaded")
|
||||||
else:
|
else:
|
||||||
DATA[item_id] = Meme(item_id, aliases, abstract, template, font, font_size, texts)
|
DATA[item_id] = meme
|
||||||
for alias in aliases:
|
for alias in meme.aliases:
|
||||||
if alias in ALIASES:
|
if alias in ALIASES:
|
||||||
logger.warning(f"Item '{item_id}'({i}): alias '{alias}' already registered by '{ALIASES[alias]}'")
|
logger.warning(f"Item '{item_id}'({i}): alias '{alias}' already registered by '{ALIASES[alias]}'")
|
||||||
else:
|
else:
|
||||||
ALIASES[alias] = item_id
|
ALIASES[alias] = item_id
|
||||||
logger.info(f"Loaded meme '{item_id}' with {len(texts)} texts")
|
logger.info(f"Loaded meme '{item_id}' with {len(meme.texts)} texts")
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
logger.warning(f"Item '{item_id}'({i}): key {e} not found")
|
logger.warning(f"Item '{item_id}'({i}): key {e} not found")
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
@@ -109,51 +89,30 @@ def load_item(i, item):
|
|||||||
logger.warning(f"Item '{item_id}'({i}): {e}")
|
logger.warning(f"Item '{item_id}'({i}): {e}")
|
||||||
|
|
||||||
|
|
||||||
def load_text(j, raw_text):
|
def load_text(j, raw_text, text=None):
|
||||||
"""
|
"""
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
:param (int) j:
|
:param (int) j:
|
||||||
:param (dict) raw_text:
|
:param (dict) raw_text:
|
||||||
|
:param (Text|None) text:
|
||||||
:raises TypeError:
|
:raises TypeError:
|
||||||
:rtype: Text
|
:rtype: Text
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if not (isinstance(raw_text, dict)):
|
if text is None:
|
||||||
raise TypeError(f"root is not a dict")
|
text = Text(f"text {j + 1}")
|
||||||
text = Text(f"text {j + 1}")
|
text.font = utils.read_key_safe(raw_text, "font", types=[str])
|
||||||
if "font" in raw_text:
|
text.x_range = utils.read_key_safe(raw_text, "x_range", types=[float, int], is_list=True, is_list_size=2)
|
||||||
if not (isinstance(raw_text["font"], str)):
|
text.y_range = utils.read_key_safe(raw_text, "y_range", types=[float, int], is_list=True, is_list_size=2)
|
||||||
raise TypeError(f"'font' is not a str")
|
text.font_size = utils.read_key_safe(raw_text, "font_size", types=[float])
|
||||||
text.font = raw_text["font"]
|
text.fill = utils.read_key_safe(raw_text, "fill", types=[int], is_list=True, is_list_size=3)
|
||||||
if "x_range" in raw_text:
|
text.stroke_width = utils.read_key_safe(raw_text, "stroke_width", types=[float])
|
||||||
if not (utils.is_list_of(raw_text["x_range"], [int, float], 2)):
|
text.stroke_fill = utils.read_key_safe(raw_text, "stroke_fill", types=[int], is_list=True, is_list_size=3)
|
||||||
raise TypeError(f"'x_range' is not a list of 2 float")
|
|
||||||
text.x_range = raw_text["x_range"]
|
|
||||||
if "y_range" in raw_text:
|
|
||||||
if not (utils.is_list_of(raw_text["y_range"], [int, float], 2)):
|
|
||||||
raise TypeError(f"'y_range' is not a list of 2 float")
|
|
||||||
text.y_range = raw_text["y_range"]
|
|
||||||
if "position" in raw_text:
|
if "position" in raw_text:
|
||||||
if raw_text["position"] not in [p.name for p in Pos]:
|
if raw_text["position"] not in [p.name for p in Pos]:
|
||||||
raise TypeError(f"'position' is not a valid position (ex: NW, E, SE, ...)")
|
raise TypeError(f"'position' is not a valid position (ex: NW, E, SE, ...)")
|
||||||
text.position = [p for p in Pos if p.name == raw_text["position"]][0]
|
text.position = [p for p in Pos if p.name == raw_text["position"]][0]
|
||||||
if "font_size" in raw_text:
|
|
||||||
if not (isinstance(raw_text["font_size"], float)):
|
|
||||||
raise TypeError(f"'font_size' is not a float")
|
|
||||||
text.font_size = raw_text["font_size"]
|
|
||||||
if "fill" in raw_text:
|
|
||||||
if not (utils.is_list_of(raw_text["fill"], [int], 3)):
|
|
||||||
raise TypeError(f"'fill' is not a list of 3 int")
|
|
||||||
text.fill = tuple(raw_text["fill"])
|
|
||||||
if "stroke_width" in raw_text:
|
|
||||||
if not (isinstance(raw_text["stroke_width"], float)):
|
|
||||||
raise TypeError(f"'stroke_width' is not a float")
|
|
||||||
text.stroke_width = raw_text["stroke_width"]
|
|
||||||
if "stroke_fill" in raw_text:
|
|
||||||
if not (utils.is_list_of(raw_text["stroke_fill"], [int], 3)):
|
|
||||||
raise TypeError(f"'stroke_fill' is not a list of 3 int")
|
|
||||||
text.stroke_fill = tuple(raw_text["stroke_fill"])
|
|
||||||
if "align" in raw_text:
|
if "align" in raw_text:
|
||||||
if raw_text["align"] not in ["left", "center", "right"]:
|
if raw_text["align"] not in ["left", "center", "right"]:
|
||||||
raise TypeError(f"'align' is not 'left', 'center' or 'right'")
|
raise TypeError(f"'align' is not 'left', 'center' or 'right'")
|
||||||
|
|||||||
+54
-23
@@ -1,6 +1,9 @@
|
|||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
DEFAULT_FONT = "arial"
|
||||||
|
DEFAULT_FONT_SIZE = 0.05
|
||||||
|
|
||||||
|
|
||||||
class Pos(IntEnum):
|
class Pos(IntEnum):
|
||||||
"""
|
"""
|
||||||
@@ -22,32 +25,33 @@ class Meme:
|
|||||||
TODO
|
TODO
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, meme_id, aliases, abstract, template, font, font_size, texts):
|
def __init__(self, meme_id, aliases=None, abstract=False, template=None, text_base=None, texts=None):
|
||||||
self.id = meme_id
|
self.id = meme_id
|
||||||
self.aliases = aliases
|
if aliases is None:
|
||||||
|
self.aliases = []
|
||||||
|
else:
|
||||||
|
self.aliases = aliases
|
||||||
self.abstract = abstract
|
self.abstract = abstract
|
||||||
self.template = template
|
self.template = template
|
||||||
self.font = font
|
if text_base is None:
|
||||||
self.font_size = font_size
|
self.text_base = Text()
|
||||||
self.texts = texts
|
else:
|
||||||
|
self.text_base = text_base
|
||||||
def clone_texts(self):
|
if texts is None:
|
||||||
return copy.deepcopy(self.texts)
|
self.texts = None
|
||||||
|
else:
|
||||||
|
self.texts = copy.deepcopy(texts)
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
return Meme(self.id,
|
return copy.deepcopy(self)
|
||||||
self.aliases,
|
|
||||||
self.abstract,
|
|
||||||
self.template,
|
|
||||||
self.font,
|
|
||||||
self.font_size,
|
|
||||||
self.clone_texts())
|
|
||||||
|
|
||||||
|
|
||||||
class Text:
|
class Text:
|
||||||
"""
|
"""
|
||||||
TODO
|
TODO
|
||||||
"""
|
"""
|
||||||
|
base_properties = ["font", "font_size", "fill", "stroke_width",
|
||||||
|
"stroke_fill", "align", "position"]
|
||||||
|
|
||||||
def __init__(self, text=None):
|
def __init__(self, text=None):
|
||||||
self.text = text
|
self.text = text
|
||||||
@@ -58,15 +62,42 @@ class Text:
|
|||||||
self.font = None
|
self.font = None
|
||||||
self.font_size = None
|
self.font_size = None
|
||||||
|
|
||||||
self.fill = (0, 0, 0)
|
self.fill = None
|
||||||
self.stroke_width = 0
|
self.stroke_width = None
|
||||||
self.stroke_fill = (0, 0, 0)
|
self.stroke_fill = None
|
||||||
|
|
||||||
self.align = "center"
|
self.align = None
|
||||||
self.position = Pos.CENTER
|
self.position = None
|
||||||
|
|
||||||
def update(self, base):
|
def update(self, base):
|
||||||
for prop in ["font", "font_size", "fill", "stroke_width",
|
"""
|
||||||
"stroke_fill", "align", "position"]:
|
TODO
|
||||||
|
|
||||||
|
:param (Text) base:
|
||||||
|
"""
|
||||||
|
for prop in Text.base_properties:
|
||||||
if getattr(self, prop) is None:
|
if getattr(self, prop) is None:
|
||||||
setattr(self,prop, getattr(base, prop))
|
setattr(self, prop, getattr(base, prop))
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
"""
|
||||||
|
TODO
|
||||||
|
"""
|
||||||
|
if self.font is None:
|
||||||
|
self.font = DEFAULT_FONT
|
||||||
|
if self.font_size is None:
|
||||||
|
self.font_size = DEFAULT_FONT_SIZE
|
||||||
|
if self.align is None:
|
||||||
|
self.align = "center"
|
||||||
|
if self.fill is None:
|
||||||
|
self.fill = (0, 0, 0)
|
||||||
|
else:
|
||||||
|
self.fill = tuple(self.fill)
|
||||||
|
if self.stroke_fill is None:
|
||||||
|
self.stroke_fill = (0, 0, 0)
|
||||||
|
else:
|
||||||
|
self.stroke_fill = tuple(self.stroke_fill)
|
||||||
|
if self.stroke_width is None:
|
||||||
|
self.stroke_width = 0
|
||||||
|
if self.position is None:
|
||||||
|
self.position = Pos.CENTER
|
||||||
|
|||||||
+51
-17
@@ -3,41 +3,86 @@ import os.path as path
|
|||||||
|
|
||||||
|
|
||||||
def relative_path(file, *args):
|
def relative_path(file, *args):
|
||||||
|
"""
|
||||||
|
TODO
|
||||||
|
|
||||||
|
:param (str) file:
|
||||||
|
:param (str) args:
|
||||||
|
:rtype str
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
return path.realpath(path.join(path.dirname(path.realpath(file)), *args))
|
return path.realpath(path.join(path.dirname(path.realpath(file)), *args))
|
||||||
|
|
||||||
|
|
||||||
def read_key_safe(d, k, default=None):
|
def read_key_safe(d, k, default=None, *, types=None, is_list=False, is_list_size=None):
|
||||||
"""
|
"""
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
:param (dict) d: source dict
|
:param (dict) d: source dict
|
||||||
:param (str) k: key to read
|
:param (str) k: key to read
|
||||||
:param default: default value
|
:param default: default value
|
||||||
|
:param (list of type|None) types: types to check
|
||||||
|
:param (bool) is_list: if the type is a list of types
|
||||||
|
:param (int|None) is_list_size: size of the list to enforce or None
|
||||||
|
:raises TypeError:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if k in d:
|
try:
|
||||||
return d[k]
|
return read_key(d, k, default, types=types, is_list=is_list, is_list_size=is_list_size)
|
||||||
else:
|
except KeyError:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def read_key(d, k, default=None):
|
def read_key(d, k, default=None, *, types=None, is_list=False, is_list_size=None):
|
||||||
"""
|
"""
|
||||||
TODO
|
TODO
|
||||||
|
|
||||||
:param (dict) d: source dict
|
:param (dict) d: source dict
|
||||||
:param (str) k: key to read
|
:param (str) k: key to read
|
||||||
:param default: default value
|
:param default: default value
|
||||||
|
:param (list of type|None) types: types to check
|
||||||
|
:param (bool) is_list: if the type is a list of types
|
||||||
|
:param (int|None) is_list_size: size of the list to enforce or None
|
||||||
|
:raises TypeError:
|
||||||
|
:raises KeyError:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if k in d:
|
if k in d:
|
||||||
return d[k]
|
v = d[k]
|
||||||
|
if types is not None:
|
||||||
|
try:
|
||||||
|
check_type(v, types, is_list, is_list_size)
|
||||||
|
except TypeError as e:
|
||||||
|
raise TypeError(f"'{k}' is {e}")
|
||||||
|
return v
|
||||||
elif default is not None:
|
elif default is not None:
|
||||||
return default
|
return default
|
||||||
else:
|
else:
|
||||||
raise KeyError(k)
|
raise KeyError(k)
|
||||||
|
|
||||||
|
|
||||||
|
def check_type(obj, types, is_list=False, is_list_size=None):
|
||||||
|
"""
|
||||||
|
TODO
|
||||||
|
|
||||||
|
:param obj:
|
||||||
|
:param (list of type|None) types: types to check
|
||||||
|
:param (bool) is_list: if the type is a list of types
|
||||||
|
:param (int|None) is_list_size: size of the list to enforce or None
|
||||||
|
:raises TypeError:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if is_list:
|
||||||
|
if not is_list_of(obj, types, is_list_size):
|
||||||
|
if is_list_size is None:
|
||||||
|
raise TypeError(f"not a list of {is_list_size} {types[0].__name__}")
|
||||||
|
else:
|
||||||
|
raise TypeError(f"not a list of {types[0].__name__}")
|
||||||
|
else:
|
||||||
|
if not is_list_of([obj], types):
|
||||||
|
raise TypeError(f"not a {types[0].__name__}")
|
||||||
|
|
||||||
|
|
||||||
def is_list_of(obj, types, length=None):
|
def is_list_of(obj, types, length=None):
|
||||||
"""
|
"""
|
||||||
TODO
|
TODO
|
||||||
@@ -63,17 +108,6 @@ def is_list_of(obj, types, length=None):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def is_url(s):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
|
|
||||||
:param (str) s:
|
|
||||||
:rtype: bool
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return False # TODO
|
|
||||||
|
|
||||||
|
|
||||||
args_regex = re.compile('"([^"]*)"|\'([^\']*)\'|([^ ]+)')
|
args_regex = re.compile('"([^"]*)"|\'([^\']*)\'|([^ ]+)')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user