text part in meme pipeline

This commit is contained in:
klemek
2020-04-28 18:11:09 +02:00
parent dffdf656dc
commit a7610c2f01
6 changed files with 86 additions and 29 deletions
+1 -1
View File
@@ -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)
+26 -3
View File
@@ -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,7 +55,25 @@ 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:
if img.mode != 'RGBA':
img = img.convert(mode='RGBA') img = img.convert(mode='RGBA')
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
for text in texts: for text in texts:
@@ -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
+26 -6
View File
@@ -24,29 +24,49 @@ 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()
if meme_id == "text":
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
else:
meme = meme_db.get_meme(meme_id) meme = meme_db.get_meme(meme_id)
if meme is None: if meme is None:
logger.warning(f"Meme template '{meme_id}' not found") logger.warning(f"Meme template '{meme_id}' not found")
@@ -62,4 +82,4 @@ def compute_part(*args: str, debug: bool = False) -> Optional[Image.Image]:
c += 1 c += 1
else: else:
meme.texts[i].text = meme.texts[meme.texts[i].text_ref].text meme.texts[i].text = meme.texts[meme.texts[i].text_ref].text
return img_factory.build_image(meme.template, meme.texts, debug=debug) return img_factory.build_from_template(meme.template, meme.texts, debug=debug)
+5
View File
@@ -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:
+13 -4
View File
@@ -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
View File
@@ -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