text part in meme pipeline
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ img_line = None
|
||||
i = None
|
||||
for i, meme_id in enumerate(id_list):
|
||||
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:
|
||||
img.save(path.join(templates_dir, meme.template))
|
||||
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.path as path
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from . import utils
|
||||
from .types import Text
|
||||
@@ -12,6 +13,8 @@ TEMPLATES_DIR = utils.relative_path(__file__, "..", "templates")
|
||||
|
||||
FONTS = {}
|
||||
|
||||
TEXT_IMAGE_WIDTH = 800
|
||||
|
||||
logger = logging.getLogger("img_factory")
|
||||
|
||||
|
||||
@@ -27,6 +30,8 @@ def load_fonts():
|
||||
|
||||
|
||||
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])
|
||||
for i, img in enumerate(images):
|
||||
if img.size[0] != width:
|
||||
@@ -40,7 +45,7 @@ def compose_image(images: List[Image.Image]) -> Image.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:
|
||||
img = Image.open(path.join(TEMPLATES_DIR, template)).convert(mode='RGBA')
|
||||
except OSError as e:
|
||||
@@ -50,8 +55,26 @@ def build_image(template: str, texts: List[Text], debug: bool = False) -> Option
|
||||
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:
|
||||
img = img.convert(mode='RGBA')
|
||||
if img.mode != 'RGBA':
|
||||
img = img.convert(mode='RGBA')
|
||||
draw = ImageDraw.Draw(img)
|
||||
for text in texts:
|
||||
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_height = round(size[1] * (text.y_range[1] - text.y_range[0]))
|
||||
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]
|
||||
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 = font.font_variant(size=font_size)
|
||||
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.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:
|
||||
return None
|
||||
|
||||
parts = utils.split_arguments(args, "-")
|
||||
images = []
|
||||
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)
|
||||
|
||||
watermarks = [right_wmark]
|
||||
if left_wmark_text is not None:
|
||||
left_wmark.text = left_wmark_text
|
||||
watermarks += [left_wmark]
|
||||
watermarks += [left_wmark.variant(left_wmark_text)]
|
||||
output_image = img_factory.apply_texts(output_image, watermarks, debug=debug)
|
||||
|
||||
return output_image
|
||||
|
||||
|
||||
def compute_part(*args: str, debug: bool = False) -> Optional[Image.Image]:
|
||||
meme_id = args[0]
|
||||
meme = meme_db.get_meme(meme_id)
|
||||
if meme is None:
|
||||
logger.warning(f"Meme template '{meme_id}' not found")
|
||||
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
|
||||
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 = meme_db.get_meme(meme_id)
|
||||
if meme is None:
|
||||
logger.warning(f"Meme template '{meme_id}' not found")
|
||||
return None
|
||||
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:
|
||||
meme.texts[i].text = ""
|
||||
c += 1
|
||||
else:
|
||||
meme.texts[i].text = meme.texts[meme.texts[i].text_ref].text
|
||||
return img_factory.build_image(meme.template, meme.texts, debug=debug)
|
||||
meme.texts[i].text = meme.texts[meme.texts[i].text_ref].text
|
||||
return img_factory.build_from_template(meme.template, meme.texts, debug=debug)
|
||||
|
||||
@@ -57,6 +57,11 @@ class Text:
|
||||
self.align = 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'):
|
||||
for prop in Text.base_properties:
|
||||
if getattr(self, prop) is None:
|
||||
|
||||
@@ -20,13 +20,22 @@ class TestText(TestCase):
|
||||
txt2.fill = [0, 1, 0]
|
||||
txt2.stroke_width = 5
|
||||
txt1.update(txt2)
|
||||
self.assertEqual("txt1", txt1.text, "text keeped")
|
||||
self.assertIsNone(txt1.angle, "angle keeped")
|
||||
self.assertEqual((0, 1), txt1.x_range, "position keeped")
|
||||
self.assertEqual("txt1", txt1.text, "text kept")
|
||||
self.assertIsNone(txt1.angle, "angle kept")
|
||||
self.assertEqual((0, 1), txt1.x_range, "position kept")
|
||||
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)
|
||||
|
||||
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):
|
||||
txt1 = types.Text("txt1")
|
||||
txt1.fill = [0, 1, 0]
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ while True:
|
||||
count = 0
|
||||
for meme_id in meme_db.LIST:
|
||||
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:
|
||||
img.save(path.join(dst_dir, meme.template))
|
||||
count += 1
|
||||
|
||||
Reference in New Issue
Block a user