text part in meme pipeline
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ img_line = None
|
|||||||
i = None
|
i = None
|
||||||
for i, meme_id in enumerate(id_list):
|
for i, meme_id in enumerate(id_list):
|
||||||
meme = meme_db.get_meme(meme_id)
|
meme = meme_db.get_meme(meme_id)
|
||||||
img = img_factory.build_image(meme.template, meme.texts, debug=True)
|
img = img_factory.build_from_template(meme.template, meme.texts, debug=True)
|
||||||
if img is not None:
|
if img is not None:
|
||||||
img.save(path.join(templates_dir, meme.template))
|
img.save(path.join(templates_dir, meme.template))
|
||||||
size = (round(img.size[0] * IMG_HEIGHT / img.size[1]), IMG_HEIGHT)
|
size = (round(img.size[0] * IMG_HEIGHT / img.size[1]), IMG_HEIGHT)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from PIL import Image, ImageFont, ImageDraw
|
|||||||
import os
|
import os
|
||||||
import os.path as path
|
import os.path as path
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .types import Text
|
from .types import Text
|
||||||
@@ -12,6 +13,8 @@ TEMPLATES_DIR = utils.relative_path(__file__, "..", "templates")
|
|||||||
|
|
||||||
FONTS = {}
|
FONTS = {}
|
||||||
|
|
||||||
|
TEXT_IMAGE_WIDTH = 800
|
||||||
|
|
||||||
logger = logging.getLogger("img_factory")
|
logger = logging.getLogger("img_factory")
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +30,8 @@ def load_fonts():
|
|||||||
|
|
||||||
|
|
||||||
def compose_image(images: List[Image.Image]) -> Image.Image:
|
def compose_image(images: List[Image.Image]) -> Image.Image:
|
||||||
|
if len(images) == 1:
|
||||||
|
return images[0]
|
||||||
width = min([img.size[0] for img in images])
|
width = min([img.size[0] for img in images])
|
||||||
for i, img in enumerate(images):
|
for i, img in enumerate(images):
|
||||||
if img.size[0] != width:
|
if img.size[0] != width:
|
||||||
@@ -40,7 +45,7 @@ def compose_image(images: List[Image.Image]) -> Image.Image:
|
|||||||
return output_image
|
return output_image
|
||||||
|
|
||||||
|
|
||||||
def build_image(template: str, texts: List[Text], debug: bool = False) -> Optional[Image.Image]:
|
def build_from_template(template: str, texts: List[Text], debug: bool = False) -> Optional[Image.Image]:
|
||||||
try:
|
try:
|
||||||
img = Image.open(path.join(TEMPLATES_DIR, template)).convert(mode='RGBA')
|
img = Image.open(path.join(TEMPLATES_DIR, template)).convert(mode='RGBA')
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@@ -50,8 +55,26 @@ def build_image(template: str, texts: List[Text], debug: bool = False) -> Option
|
|||||||
return img
|
return img
|
||||||
|
|
||||||
|
|
||||||
|
def build_text_only(texts: List[Text], debug: bool = False) -> Image.Image:
|
||||||
|
heights = []
|
||||||
|
for text in texts:
|
||||||
|
text.init()
|
||||||
|
text.text, font = fit_text((TEXT_IMAGE_WIDTH, sys.maxsize), text)
|
||||||
|
text_size = font.getsize_multiline(text.text, stroke_width=text.stroke_width * font.size)
|
||||||
|
heights += [round(text_size[1] / (text.y_range[1] - text.y_range[0]))]
|
||||||
|
max_height = sum(heights)
|
||||||
|
for i, text in enumerate(texts):
|
||||||
|
range_factor = heights[i] / max_height
|
||||||
|
start = sum(heights[:i]) / max_height
|
||||||
|
text.y_range = (start + text.y_range[0] * range_factor, start + text.y_range[1] * range_factor)
|
||||||
|
pass
|
||||||
|
txt_img = Image.new('RGBA', (TEXT_IMAGE_WIDTH, max_height), (255, 255, 255))
|
||||||
|
return apply_texts(txt_img, texts, debug=debug)
|
||||||
|
|
||||||
|
|
||||||
def apply_texts(img: Image.Image, texts: List[Text], debug: bool = False) -> Image.Image:
|
def apply_texts(img: Image.Image, texts: List[Text], debug: bool = False) -> Image.Image:
|
||||||
img = img.convert(mode='RGBA')
|
if img.mode != 'RGBA':
|
||||||
|
img = img.convert(mode='RGBA')
|
||||||
draw = ImageDraw.Draw(img)
|
draw = ImageDraw.Draw(img)
|
||||||
for text in texts:
|
for text in texts:
|
||||||
draw_text(draw, img, text, debug=debug)
|
draw_text(draw, img, text, debug=debug)
|
||||||
@@ -95,10 +118,10 @@ def fit_text(size: Tuple[int, int], text: Text) -> Tuple[str, ImageFont.FreeType
|
|||||||
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
|
||||||
font_size = round(text.font_size * min(size)) + 1
|
font_size = round(text.font_size * size[0]) + 1
|
||||||
font = FONTS[text.font]
|
font = FONTS[text.font]
|
||||||
text_content = ""
|
text_content = ""
|
||||||
while (text_size is None or text_size[0] >= max_width or text_size[1] >= max_height) and font_size > 1:
|
while (text_size is None or text_size[0] > max_width or text_size[1] > max_height) and font_size > 1:
|
||||||
font_size -= 1
|
font_size -= 1
|
||||||
font = font.font_variant(size=font_size)
|
font = font.font_variant(size=font_size)
|
||||||
n_lines = 0
|
n_lines = 0
|
||||||
|
|||||||
+39
-19
@@ -24,42 +24,62 @@ left_wmark.font_size = 0.02
|
|||||||
left_wmark.x_range = [0.005, 0.995]
|
left_wmark.x_range = [0.005, 0.995]
|
||||||
left_wmark.y_range = [0.005, 0.995]
|
left_wmark.y_range = [0.005, 0.995]
|
||||||
|
|
||||||
|
simple_text = Text()
|
||||||
|
simple_text.align = "left"
|
||||||
|
simple_text.position = Pos.W
|
||||||
|
simple_text.font_size = 0.04
|
||||||
|
simple_text.x_range = [0.01, 0.99]
|
||||||
|
simple_text.y_range = [0.2, 0.8]
|
||||||
|
|
||||||
def compute(*args: str, left_wmark_text: Optional[Text] = None, debug: bool = False) -> Optional[Image.Image]:
|
|
||||||
|
def compute(*args: str, left_wmark_text: Optional[str] = None, debug: bool = False) -> Optional[Image.Image]:
|
||||||
if len(args) < 1:
|
if len(args) < 1:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
parts = utils.split_arguments(args, "-")
|
parts = utils.split_arguments(args, "-")
|
||||||
images = []
|
images = []
|
||||||
for part in parts:
|
for part in parts:
|
||||||
images += [compute_part(*part, debug=debug)]
|
img = compute_part(*part, debug=debug)
|
||||||
|
if img is not None:
|
||||||
|
images += [img]
|
||||||
|
|
||||||
|
if len(images) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
output_image = img_factory.compose_image(images)
|
output_image = img_factory.compose_image(images)
|
||||||
|
|
||||||
watermarks = [right_wmark]
|
watermarks = [right_wmark]
|
||||||
if left_wmark_text is not None:
|
if left_wmark_text is not None:
|
||||||
left_wmark.text = left_wmark_text
|
watermarks += [left_wmark.variant(left_wmark_text)]
|
||||||
watermarks += [left_wmark]
|
|
||||||
output_image = img_factory.apply_texts(output_image, watermarks, debug=debug)
|
output_image = img_factory.apply_texts(output_image, watermarks, debug=debug)
|
||||||
|
|
||||||
return output_image
|
return output_image
|
||||||
|
|
||||||
|
|
||||||
def compute_part(*args: str, debug: bool = False) -> Optional[Image.Image]:
|
def compute_part(*args: str, debug: bool = False) -> Optional[Image.Image]:
|
||||||
meme_id = args[0]
|
meme_id = args[0].lower().strip()
|
||||||
meme = meme_db.get_meme(meme_id)
|
|
||||||
if meme is None:
|
if meme_id == "text":
|
||||||
logger.warning(f"Meme template '{meme_id}' not found")
|
if len(args) < 2:
|
||||||
|
return None
|
||||||
|
texts = [simple_text.variant(arg) for arg in args[1:]]
|
||||||
|
return img_factory.build_text_only(texts, debug=debug)
|
||||||
|
elif meme_id == "image":
|
||||||
return None
|
return None
|
||||||
if len(args) > 1:
|
else:
|
||||||
c = 0
|
meme = meme_db.get_meme(meme_id)
|
||||||
for i in range(len(meme.texts)):
|
if meme is None:
|
||||||
if meme.texts[i].text_ref is None:
|
logger.warning(f"Meme template '{meme_id}' not found")
|
||||||
if c < len(args) - 1:
|
return None
|
||||||
meme.texts[i].text = args[c + 1].replace("\\n", "\n")
|
if len(args) > 1:
|
||||||
|
c = 0
|
||||||
|
for i in range(len(meme.texts)):
|
||||||
|
if meme.texts[i].text_ref is None:
|
||||||
|
if c < len(args) - 1:
|
||||||
|
meme.texts[i].text = args[c + 1].replace("\\n", "\n")
|
||||||
|
else:
|
||||||
|
meme.texts[i].text = ""
|
||||||
|
c += 1
|
||||||
else:
|
else:
|
||||||
meme.texts[i].text = ""
|
meme.texts[i].text = meme.texts[meme.texts[i].text_ref].text
|
||||||
c += 1
|
return img_factory.build_from_template(meme.template, meme.texts, debug=debug)
|
||||||
else:
|
|
||||||
meme.texts[i].text = meme.texts[meme.texts[i].text_ref].text
|
|
||||||
return img_factory.build_image(meme.template, meme.texts, debug=debug)
|
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ class Text:
|
|||||||
self.align = None
|
self.align = None
|
||||||
self.position = None
|
self.position = None
|
||||||
|
|
||||||
|
def variant(self, text: str) -> 'Text':
|
||||||
|
new_text = copy.deepcopy(self)
|
||||||
|
new_text.text = text
|
||||||
|
return new_text
|
||||||
|
|
||||||
def update(self, base: 'Text'):
|
def update(self, base: 'Text'):
|
||||||
for prop in Text.base_properties:
|
for prop in Text.base_properties:
|
||||||
if getattr(self, prop) is None:
|
if getattr(self, prop) is None:
|
||||||
|
|||||||
@@ -20,13 +20,22 @@ class TestText(TestCase):
|
|||||||
txt2.fill = [0, 1, 0]
|
txt2.fill = [0, 1, 0]
|
||||||
txt2.stroke_width = 5
|
txt2.stroke_width = 5
|
||||||
txt1.update(txt2)
|
txt1.update(txt2)
|
||||||
self.assertEqual("txt1", txt1.text, "text keeped")
|
self.assertEqual("txt1", txt1.text, "text kept")
|
||||||
self.assertIsNone(txt1.angle, "angle keeped")
|
self.assertIsNone(txt1.angle, "angle kept")
|
||||||
self.assertEqual((0, 1), txt1.x_range, "position keeped")
|
self.assertEqual((0, 1), txt1.x_range, "position kept")
|
||||||
self.assertEqual(txt2.fill, txt1.fill, "fill changed")
|
self.assertEqual(txt2.fill, txt1.fill, "fill changed")
|
||||||
self.assertNotEqual(txt2.stroke_width, txt1.stroke_width, "stroke_width keeped")
|
self.assertNotEqual(txt2.stroke_width, txt1.stroke_width, "stroke_width kept")
|
||||||
self.assertEqual(6, txt1.stroke_width)
|
self.assertEqual(6, txt1.stroke_width)
|
||||||
|
|
||||||
|
def test_variant(self):
|
||||||
|
txt1 = types.Text("txt1")
|
||||||
|
txt1.stroke_width = 6
|
||||||
|
txt1.x_range = (0.5, 0.8)
|
||||||
|
txt2 = txt1.variant("txt2")
|
||||||
|
self.assertEqual("txt2", txt2.text, "text changed")
|
||||||
|
self.assertIsNone(txt2.angle, "angle kept")
|
||||||
|
self.assertEqual((0.5, 0.8), txt2.x_range, "position kept")
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
txt1 = types.Text("txt1")
|
txt1 = types.Text("txt1")
|
||||||
txt1.fill = [0, 1, 0]
|
txt1.fill = [0, 1, 0]
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ while True:
|
|||||||
count = 0
|
count = 0
|
||||||
for meme_id in meme_db.LIST:
|
for meme_id in meme_db.LIST:
|
||||||
meme = meme_db.get_meme(meme_id)
|
meme = meme_db.get_meme(meme_id)
|
||||||
img = img_factory.build_image(meme.template, meme.texts, debug=True)
|
img = img_factory.build_from_template(meme.template, meme.texts, debug=True)
|
||||||
if img is not None:
|
if img is not None:
|
||||||
img.save(path.join(dst_dir, meme.template))
|
img.save(path.join(dst_dir, meme.template))
|
||||||
count += 1
|
count += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user