From 5e3f0ab337dec07f6a76f10267241f1ba1daa40a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jan 2020 19:11:05 +0100 Subject: [PATCH 01/42] initial commit --- pype/scripts/slate/base.py | 799 +++++++++++++++++++++++++++++++++++++ 1 file changed, 799 insertions(+) create mode 100644 pype/scripts/slate/base.py diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py new file mode 100644 index 0000000000..cd51c94aab --- /dev/null +++ b/pype/scripts/slate/base.py @@ -0,0 +1,799 @@ +import sys +sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") +from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor +from uuid import uuid4 + +class BaseObj: + """Base Object for slates.""" + + obj_type = None + available_parents = [] + + def __init__(self, parent, style={}, name=None): + if not self.obj_type: + raise NotImplemented( + "Class don't have set object type <{}>".format( + self.__class__.__name__ + ) + ) + + parent_obj_type = None + if parent: + parent_obj_type = parent.obj_type + + if parent_obj_type not in self.available_parents: + expected_parents = ", ".join(self.available_parents) + raise Exception(( + "Invalid parent <{}> for <{}>. Expected <{}>" + ).format( + parent.__class__.__name__, self.obj_type, expected_parents + )) + + self.parent = parent + self._style = style + + self.id = uuid4() + self.parent = parent + self.name = name + self._style = style + self.items = {} + self.final_style = None + + @property + def main_style(self): + default_style_v1 = { + "*": { + "font-family": "arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": False, + "font-italic": False, + "bg-color": None, + "bg-alter-color": None, + "alignment-vertical": "left", #left, center, right + "alignment-horizontal": "top", #top, center, bottom + "padding": 0, + # "padding-left": 0, + # "padding-right": 0, + # "padding-top": 0, + # "padding-bottom": 0 + "margin": 0, + # "margin-left": 0, + # "margin-right": 0, + # "margin-top": 0, + # "margin-bottom": 0 + }, + "layer": {}, + "image": {}, + "text": {}, + "table": {}, + "table-item": {}, + "table-item-col-0": {}, + "#MyName": {} + } + return default_style_v1 + + @property + def is_drawing(self): + return self.parent.is_drawing + + @property + def height(self): + raise NotImplementedError( + "Attribute `height` is not implemented for <{}>".format( + self.__clas__.__name__ + ) + ) + + @property + def width(self): + raise NotImplementedError( + "Attribute `width` is not implemented for <{}>".format( + self.__clas__.__name__ + ) + ) + + @property + def full_style(self): + if self.is_drawing and self.final_style is not None: + return self.final_style + + if self.parent is not None: + style = dict(val for val in self.parent.full_style.items()) + else: + style = self.main_style + + for key, value in self._style.items(): + if key in style: + style[key] = value + style.update() + + if self.is_drawing: + self.final_style = style + + return style + + @property + def style(self): + style = self.full_style + style.update(self._style) + + base = style.get("*", style.get("global")) or {} + obj_specific = style.get(self.obj_type) or {} + name_specific = {} + if self.name: + name = str(self.name) + if not name.startswith("#"): + name += "#" + name_specific = style.get(name) or {} + + output = {} + output.update(base) + output.update(obj_specific) + output.update(name_specific) + + return output + + @property + def pos_x(self): + return 0 + + @property + def pos_y(self): + return 0 + + @property + def pos_start(self): + return (self.pos_x, self.pos_y) + + @property + def pos_end(self): + pos_x, pos_y = self.pos_start + pos_x += self.width + pos_y += self.height + return (pos_x, pos_y) + + @property + def max_width(self): + return self.style.get("max-width") or (self.width * 1) + + @property + def max_height(self): + return self.style.get("max-height") or (self.height * 1) + + @property + def max_content_width(self): + width = self.max_width + padding = self.style["padding"] + padding_left = self.style.get("padding-left") or padding + padding_right = self.style.get("padding-right") or padding + return (width - (padding_left + padding_right)) + + @property + def max_content_height(self): + height = self.max_height + padding = self.style["padding"] + padding_top = self.style.get("padding-top") or padding + padding_bottom = self.style.get("padding-bottom") or padding + return (height - (padding_top + padding_bottom)) + + def add_item(self, item): + self.items[item.id] = item + + def reset(self): + self.final_style = None + for item in self.items.values(): + item.reset() + + +class MainFrame(BaseObj): + + obj_type = "main_frame" + available_parents = [None] + + def __init__(self, width, height, style={}, parent=None, name=None): + super(MainFrame, self).__init__(parent, style, name) + self._width = width + self._height = height + self._is_drawing = False + + @property + def width(self): + return self._width + + @property + def height(self): + return self._height + + @property + def is_drawing(self): + return self._is_drawing + + def draw(self, path): + self._is_drawing = True + bg_color = self.style["bg-color"] + image = Image.new("RGB", (self.width, self.height))#, color=bg_color) + for item in self.items.values(): + item.draw(image) + + image.save(path) + self._is_drawing = False + self.reset() + + + +class Layer(BaseObj): + obj_type = "layer" + available_parents = ["main_frame", "layer"] + + # Direction can be 0=horizontal/ 1=vertical + def __init__(self, *args, **kwargs): + super(Layer, self).__init__(*args, **kwargs) + + @property + def pos_x(self): + if self.parent.obj_type == self.obj_type: + pos_x = self.parent.item_pos_x(self.id) + else: + pos_x = self.parent.pos_x + return pos_x + + @property + def pos_y(self): + if self.parent.obj_type == self.obj_type: + pos_y = self.parent.item_pos_y(self.id) + else: + pos_y = self.parent.pos_y + return pos_y + + @property + def direction(self): + direction = self.style.get("direction", 0) + if direction not in (0, 1): + raise Exception( + "Direction must be 0 or 1 (0 is Vertical / 1 is horizontal)!" + ) + return direction + + def item_pos_x(self, item_id): + pos_x = self.pos_x + if self.direction != 0: + for id, item in self.items.items(): + if id == item_id: + break + pos_x += item.width + if item.obj_type != "image": + pos_x += 1 + + if pos_x != self.pos_x: + pos_x += 1 + return pos_x + + def item_pos_y(self, item_id): + pos_y = self.pos_y + if self.direction != 1: + for id, item in self.items.items(): + if item_id == id: + break + pos_y += item.height + if item.obj_type != "image": + pos_y += 1 + + return pos_y + + @property + def height(self): + height = 0 + for item in self.items.values(): + if self.direction == 0: + if height > item.height: + continue + + # times 1 because won't get object pointer but number + height = item.height * 1 + else: + height += item.height + + min_height = self.style.get("min-height") + if min_height > height: + return min_height + return height + + @property + def width(self): + width = 0 + for item in self.items.values(): + if self.direction == 0: + if width > item.width: + continue + + # times 1 because won't get object pointer but number + width = item.width * 1 + else: + width += item.width + + min_width = self.style.get("min-width") + if min_width > width: + return min_width + return width + + def draw(self, image, drawer=None): + if drawer is None: + drawer = ImageDraw.Draw(image) + + for item in self.items.values(): + item.draw(image, drawer) + + +class BaseItem(BaseObj): + available_parents = ["layer"] + + def __init__(self, *args, **kwargs): + super(BaseItem, self).__init__(*args, **kwargs) + + @property + def pos_x(self): + return self.parent.item_pos_x(self.id) + + @property + def pos_y(self): + return self.parent.item_pos_y(self.id) + + @property + def content_pos_x(self): + padding = self.style["padding"] + padding_left = self.style.get("padding-left") or padding + + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + + return self.pos_x + margin_left + padding_left + + @property + def content_pos_y(self): + padding = self.style["padding"] + padding_top = self.style.get("padding-top") or padding + + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + + return self.pos_y + margin_top + padding_top + + @property + def content_width(self): + raise NotImplementedError( + "Attribute is not implemented" + ) + + @property + def content_height(self): + raise NotImplementedError( + "Attribute is not implemented" + ) + + @property + def width(self): + width = self.content_width + + padding = self.style["padding"] + padding_left = self.style.get("padding-left") or padding + padding_right = self.style.get("padding-right") or padding + + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + margin_right = self.style.get("margin-right") or margin + + return ( + width + + margin_left + margin_right + + padding_left + padding_right + ) + + @property + def height(self): + height = self.content_height + + padding = self.style["padding"] + padding_top = self.style.get("padding-top") or padding + padding_bottom = self.style.get("padding-bottom") or padding + + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + margin_bottom = self.style.get("margin-bottom") or margin + + return ( + height + + margin_bottom + margin_top + + padding_top + padding_bottom + ) + + def add_item(self, *args, **kwargs): + raise Exception("Can't add item to an item, use layers instead.") + + def draw(self, image, drawer): + raise NotImplementedError( + "Method `draw` is not implemented for <{}>".format( + self.__clas__.__name__ + ) + ) + +class ItemImage(BaseItem): + obj_type = "image" + + def __init__(self, image_path, *args, **kwargs): + super(ItemImage, self).__init__(*args, **kwargs) + self.image_path = image_path + + def draw(self, image, drawer): + paste_image = Image.open(self.image_path) + paste_image = paste_image.resize( + (self.content_width, self.content_height), + Image.ANTIALIAS + ) + image.paste( + paste_image, + (self.content_pos_x, self.content_pos_y) + ) + + @property + def content_width(self): + return self.style.get("width") + + @property + def content_height(self): + return self.style.get("height") + + +class ItemText(BaseItem): + obj_type = "text" + + def __init__(self, value, *args, **kwargs): + super(ItemText, self).__init__(*args, **kwargs) + self.value = value + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + if bg_color and bg_color.lower() != "transparent": + padding = self.style["padding"] + + padding_left = self.style.get("padding-left") or padding + padding_right = self.style.get("padding-right") or padding + padding_top = self.style.get("padding-top") or padding + padding_bottom = self.style.get("padding-bottom") or padding + # TODO border outline styles + drawer.rectangle( + (self.pos_start, self.pos_end), + fill=bg_color, + outline=None + ) + + text_pos_start = (self.content_pos_x, self.content_pos_y) + + font_color = self.style["font-color"] + font_family = self.style["font-family"] + font_size = self.style["font-size"] + + font = ImageFont.truetype(font_family, font_size) + drawer.text( + text_pos_start, + self.value, + font=font, + fill=font_color + ) + + @property + def content_width(self): + font_family = self.style["font-family"] + font_size = self.style["font-size"] + + font = ImageFont.truetype(font_family, font_size) + width = font.getsize(self.value)[0] + return width + + @property + def content_height(self): + font_family = self.style["font-family"] + font_size = self.style["font-size"] + + font = ImageFont.truetype(font_family, font_size) + height = font.getsize(self.value)[1] + return height + + +class ItemTable(BaseItem): + + obj_type = "table" + + def __init__(self, values, *args, **kwargs): + super(ItemTable, self).__init__(*args, **kwargs) + self.size_values = None + self.values_by_cords = None + + self.prepare_values(values) + self.calculate_sizes() + + def prepare_values(self, _values): + values = [] + values_by_cords = [] + for row_idx, row in enumerate(_values): + if len(values_by_cords) < row_idx + 1: + for i in range(len(values_by_cords), row_idx + 1): + values_by_cords.append([]) + + for col_idx, col in enumerate(_values[row_idx]): + if len(values_by_cords[row_idx]) < col_idx + 1: + for i in range(len(values_by_cords[row_idx]), col_idx + 1): + values_by_cords[row_idx].append("") + + if not col: + col = "" + col_item = TableField(row_idx, col_idx, col, self) + values_by_cords[row_idx][col_idx] = col_item + values.append(col_item) + + self.values = values + self.values_by_cords = values_by_cords + + def calculate_sizes(self): + row_heights = [] + col_widths = [] + for row_idx, row in enumerate(self.values_by_cords): + row_heights.append(0) + for col_idx, col_item in enumerate(row): + if len(col_widths) < col_idx + 1: + col_widths.append(0) + + _width = col_widths[col_idx] + item_width = col_item.width + if _width < item_width: + col_widths[col_idx] = item_width + + _height = row_heights[row_idx] + item_height = col_item.height + if _height < item_height: + row_heights[row_idx] = item_height + + self.size_values = (row_heights, col_widths) + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + if bg_color and bg_color.lower() != "transparent": + padding = self.style["padding"] + + padding_left = self.style.get("padding-left") or padding + padding_right = self.style.get("padding-right") or padding + padding_top = self.style.get("padding-top") or padding + padding_bottom = self.style.get("padding-bottom") or padding + # TODO border outline styles + drawer.rectangle( + (self.pos_start, self.pos_end), + fill=bg_color, + outline=None + ) + + for value in self.values: + value.draw(image, drawer) + + @property + def content_width(self): + row_heights, col_widths = self.size_values + width = 0 + for _width in col_widths: + width += _width + + if width != 0: + width -= 1 + return width + + @property + def content_height(self): + row_heights, col_widths = self.size_values + height = 0 + for _height in row_heights: + height += _height + + if height != 0: + height -= 1 + return height + + def pos_info_by_cord(self, cord_x, cord_y): + row_heights, col_widths = self.size_values + pos_x = self.pos_x + pos_y = self.pos_y + width = 0 + height = 0 + for idx, value in enumerate(col_widths): + if cord_y == idx: + width = value + break + pos_x += value + + for idx, value in enumerate(row_heights): + if cord_x == idx: + height = value + break + pos_y += value + + padding = self.style["padding"] + + padding_left = self.style.get("padding-left") or padding + padding_top = self.style.get("padding-top") or padding + pos_x += padding_left + pos_y += padding_top + return (pos_x, pos_y, width, height) + + def filled_style(self, cord_x, cord_y): + # TODO CHANGE STYLE BY CORDS + return self.style + + +class TableField(BaseItem): + + obj_type = "table-item" + available_parents = ["table"] + + def __init__(self, cord_x, cord_y, value, *args, **kwargs): + super(TableField, self).__init__(*args, **kwargs) + self.cord_x = cord_x + self.cord_y = cord_y + self.value = value + + @property + def content_width(self): + font_family = self.style["font-family"] + font_size = self.style["font-size"] + + font = ImageFont.truetype(font_family, font_size) + width = font.getsize(self.value)[0] + 1 + return width + + @property + def content_height(self): + font_family = self.style["font-family"] + font_size = self.style["font-size"] + + font = ImageFont.truetype(font_family, font_size) + height = font.getsize(self.value)[1] + 1 + return height + + def content_pos_x(self, pos_x, width): + padding = self.style["padding"] + padding_left = self.style.get("padding-left") or padding + return pos_x + padding_left + + def content_pos_y(self, pos_y, height): + padding = self.style["padding"] + padding_top = self.style.get("padding-top") or padding + return pos_y + padding_top + + def draw(self, image, drawer): + pos_x, pos_y, width, height = ( + self.parent.pos_info_by_cord(self.cord_x, self.cord_y) + ) + pos_start = (pos_x, pos_y) + pos_end = (pos_x + width, pos_y + height) + bg_color = self.style["bg-color"] + if bg_color and bg_color.lower() != "transparent": + padding = self.style["padding"] + + padding_left = self.style.get("padding-left") or padding + padding_right = self.style.get("padding-right") or padding + padding_top = self.style.get("padding-top") or padding + padding_bottom = self.style.get("padding-bottom") or padding + # TODO border outline styles + drawer.rectangle( + (pos_start, pos_end), + fill=bg_color, + outline=None + ) + + text_pos_start = ( + self.content_pos_x(pos_x, width), + self.content_pos_y(pos_y, height) + ) + + font_color = self.style["font-color"] + font_family = self.style["font-family"] + font_size = self.style["font-size"] + + font = ImageFont.truetype(font_family, font_size) + drawer.text( + text_pos_start, + self.value, + font=font, + fill=font_color + ) + +if __name__ == "__main__": + main_style = { + "bg-color": "#777777" + } + text_1_style = { + "padding": 10, + "bg-color": "#00ff77" + } + text_2_style = { + "padding": 8, + "bg-color": "#ff0066" + } + text_3_style = { + "padding": 0, + "bg-color": "#ff5500" + } + main = MainFrame(1920, 1080, style=main_style) + layer = Layer(parent=main) + main.add_item(layer) + + text_1 = ItemText("Testing message 1", layer, text_1_style) + text_2 = ItemText("Testing message 2", layer, text_2_style) + text_3 = ItemText("Testing message 3", layer, text_3_style) + table_1_items = [["0", "Output text 1"], ["1", "Output text 2"], ["2", "Output text 3"]] + table_1_style = { + "padding": 8, + "bg-color": "#0077ff" + } + table_1 = ItemTable(table_1_items, layer, table_1_style) + + image_1_style = { + "width": 240, + "height": 120, + "bg-color": "#7733aa" + } + image_1_path = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\kitten.jpg" + image_1 = ItemImage(image_1_path, layer, image_1_style) + + layer.add_item(text_1) + layer.add_item(text_2) + layer.add_item(table_1) + layer.add_item(image_1) + layer.add_item(text_3) + dst = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\test_output2.png" + main.draw(dst) + + print("*** Drawing done :)") + + +style_schema = { + "alignment": { + "description": "Alignment of item", + "type": "string", + "enum": ["left", "right", "center"], + "example": "left" + }, + "font-family": { + "description": "Font type", + "type": "string", + "example": "Arial" + }, + "font-size": { + "description": "Font size", + "type": "integer", + "example": 26 + }, + "font-color": { + "description": "Font color", + "type": "string", + "regex": ( + "^[#]{1}[a-fA-F0-9]{1}$" + " | ^[#]{1}[a-fA-F0-9]{3}$" + " | ^[#]{1}[a-fA-F0-9]{6}$" + ), + "example": "#ffffff" + }, + "font-bold": { + "description": "Font boldness", + "type": "boolean", + "example": True + }, + "bg-color": { + "description": "Background color, None means transparent", + "type": ["string", None], + "regex": ( + "^[#]{1}[a-fA-F0-9]{1}$" + " | ^[#]{1}[a-fA-F0-9]{3}$" + " | ^[#]{1}[a-fA-F0-9]{6}$" + ), + "example": "#333" + }, + "bg-alter-color": None, +} From 89a0dd05fd325053df72163ed4727acfd16b8d77 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jan 2020 19:11:50 +0100 Subject: [PATCH 02/42] added set.json need rework --- pype/scripts/slate/set.json | 59 +++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 pype/scripts/slate/set.json diff --git a/pype/scripts/slate/set.json b/pype/scripts/slate/set.json new file mode 100644 index 0000000000..e8bba8ac41 --- /dev/null +++ b/pype/scripts/slate/set.json @@ -0,0 +1,59 @@ +{ + "width": 1920, + "height": 1020, + "bg-color": "#000000", + "__bg-color__": "For setting constant color of background. May cause issues if not set when there are not painted spaces.", + "bg-image": null, + "__bg-image__": "May be path to static image???", + "defaults": { + "font-family": "Arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": false, + "bg-color": null, + "bg-alter-color": null, + "alignment": "left", + "__alignment[enum]__": ["left", "right", "center"] + }, + "items": [{ + "rel_pos_x": 0.1, + "rel_pos_y": 0.1, + "rel_width": 0.5, + "type": "table", + "col-format": [ + { + "font-size": 12, + "font-color": "#777777", + "alignment": "right" + }, + { + "alignment": "left" + } + ], + "rows": [ + [ + { + "name": "Version", + }, + { + "value": "mei_101_001_0020_slate_NFX_v001", + "font-bold": true + } + ], [ + { + "value": "Date:" + }, + { + "value": "{date}" + } + ] + ] + }, { + "rel_pos_x": 0.1, + "rel_pos_y": 0.1, + "rel_width": 0.5, + "rel_height": 0.5, + "type": "image", + } + ] +} From 18db21c82837ea80f1f6e93f1290443b8489784a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 10 Jan 2020 19:12:03 +0100 Subject: [PATCH 03/42] first successfull image inspiration --- pype/scripts/slate/slate.py | 355 ++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 pype/scripts/slate/slate.py diff --git a/pype/scripts/slate/slate.py b/pype/scripts/slate/slate.py new file mode 100644 index 0000000000..4e66f68d21 --- /dev/null +++ b/pype/scripts/slate/slate.py @@ -0,0 +1,355 @@ +import textwrap +from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor + + +class TableDraw: + def __init__( + self, image_width, image_height, + rel_pos_x, rel_pos_y, rel_width, + col_fonts=None, col_font_colors=None, + default_font=None, default_font_color=None, + col_alignments=None, rel_col_widths=None, bg_color=None, + alter_bg_color=None, pad=20, + pad_top=None, pad_bottom=None, pad_left=None, pad_right=None + ): + self.image_width = image_width + + pos_x = image_width * rel_pos_x + pos_y = image_height * rel_pos_y + width = image_width * rel_width + + self.pos_x_start = pos_x + self.pos_y_start = pos_y + self.pos_x_end = pos_x + width + self.width = width + + self.rel_col_widths = list(rel_col_widths) + self._col_widths = None + self._col_alignments = col_alignments + + if bg_color and isinstance(bg_color, str): + bg_color = ImageColor.getrgb(bg_color) + + if alter_bg_color and isinstance(alter_bg_color, str): + alter_bg_color = ImageColor.getrgb(alter_bg_color) + + self._bg_color = bg_color + self._alter_bg_color = alter_bg_color + + self.alter_use = False + + if col_fonts: + _col_fonts = [] + for col_font in col_fonts: + font_name, font_size = col_font + font = ImageFont.truetype(font_name, font_size) + _col_fonts.append(font) + col_fonts = _col_fonts + + self._col_fonts = col_fonts + + self._col_font_colors = col_font_colors + + if not default_font: + default_font = ImageFont.truetype("times", 26) + self.default_font = default_font + + if not default_font_color: + default_font_color = "#ffffff" + self.default_font_color = default_font_color + + self.texts = [] + + if pad is None: + pad = 5 + + _pad_top = pad + if pad_top is not None: + _pad_top = pad_top + + _pad_bottom = pad + if pad_bottom is not None: + _pad_bottom = pad_bottom + + _pad_left = pad + if pad_left is not None: + _pad_left = pad_left + + _pad_right = pad + if pad_right is not None: + _pad_right = pad_right + + self.pad_top = _pad_top + self.pad_bottom = _pad_bottom + self.pad_left = _pad_left + self.pad_right = _pad_right + + @property + def col_widths(self): + if self._col_widths is None: + sum_width = 0 + for w in self.rel_col_widths: + sum_width += w + + one_piece = self.width / sum_width + self._col_widths = [] + for w in self.rel_col_widths: + self._col_widths.append(one_piece * w) + + return self._col_widths + + @property + def col_fonts(self): + if self._col_fonts is None: + self._col_fonts = [] + for _ in range(len(self.col_widths)): + self._col_fonts.append(self.default_font) + + elif len(self._col_fonts) < len(self.col_widths): + if isinstance(self._col_fonts, tuple): + self._col_fonts = list(self._col_fonts) + + while len(self._col_fonts) < len(self.col_widths): + self._col_fonts.append(self.default_font) + + return self._col_fonts + + @property + def col_font_colors(self): + if self._col_font_colors is None: + self._col_font_colors = [] + for _ in range(len(self.col_widths)): + self._col_font_colors.append(self.default_font_color) + + elif len(self._col_font_colors) < len(self.col_widths): + if isinstance(self._col_font_colors, tuple): + self._col_font_colors = list(self._col_font_colors) + + while len(self._col_font_colors) < len(self.col_widths): + self._col_font_colors.append(self.default_font_color) + + return self._col_font_colors + + @property + def col_alignments(self): + if self._col_alignments is None: + self._col_alignments = [] + for _ in range(len(self.col_widths)): + self._col_alignments.append("left") + + elif len(self._col_alignments) < len(self.col_widths): + if isinstance(self._col_alignments, tuple): + self._col_alignments = list(self._col_alignments) + + while len(self._col_alignments) < len(self.col_widths): + self._col_alignments.append("left") + + return self._col_alignments + + @property + def bg_color(self): + if self.alter_use is True: + value = self.alter_bg_color + self.alter_use = False + else: + value = self._bg_color + self.alter_use = True + return value + + @property + def alter_bg_color(self): + if self._alter_bg_color: + return self._alter_bg_color + return self.bg_color + + def add_texts(self, texts): + if isinstance(texts, str): + texts = [texts] + + for text in texts: + if isinstance(text, str): + text = [text] + + if len(text) > len(self.rel_col_widths): + for _ in (len(text) - len(self.rel_col_widths)): + self.rel_col_widths.append(1) + for _t in self.texts: + _t.append("") + + self.texts.append(text) + + def draw(self, drawer): + y_pos = self.pos_y_start + for texts in self.texts: + max_height = None + cols_data = [] + for _idx, col in enumerate(texts): + width = self.col_widths[_idx] + font = self.col_fonts[_idx] + lines, line_height = self.lines_height_by_width( + drawer, col, width - self.pad_left - self.pad_right, font + ) + row_height = line_height * len(lines) + if max_height is None or row_height > max_height: + max_height = row_height + + cols_data.append({ + "lines": lines, + "line_height": line_height + }) + + drawer.rectangle( + ( + (self.pos_x_start, y_pos), + ( + self.pos_x_end, + y_pos + max_height + self.pad_top + self.pad_bottom + ) + ), + fill=self.bg_color + ) + + pos_x_start = self.pos_x_start + self.pad_left + for col, col_data in enumerate(cols_data): + lines = col_data["lines"] + line_height = col_data["line_height"] + alignment = self.col_alignments[col] + x_offset = self.col_widths[col] + font = self.col_fonts[col] + font_color = self.col_font_colors[col] + for idx, line_data in enumerate(lines): + line = line_data["text"] + line_width = line_data["width"] + if alignment == "left": + x_start = pos_x_start + self.pad_left + elif alignment == "right": + x_start = ( + pos_x_start + x_offset - line_width - + self.pad_right - self.pad_left + ) + else: + # TODO else + x_start = pos_x_start + self.pad_left + + drawer.text( + ( + x_start, + y_pos + (idx * line_height) + self.pad_top + ), + line, + font=font, + fill=font_color + ) + pos_x_start += x_offset + + y_pos += max_height + self.pad_top + self.pad_bottom + + def lines_height_by_width(self, drawer, text, width, font): + lines = [] + lines.append([part for part in text.split() if part]) + + line = 0 + while True: + thistext = lines[line] + line = line + 1 + if not thistext: + break + newline = [] + + while True: + _textwidth = drawer.textsize(" ".join(thistext), font)[0] + if ( + _textwidth <= width + ): + break + elif _textwidth > width and len(thistext) == 1: + # TODO raise error? + break + + val = thistext.pop(-1) + + if not val: + break + newline.insert(0, val) + + if len(newline) > 0: + lines.append(newline) + else: + break + + _lines = [] + height = None + for line_items in lines: + line = " ".join(line_items) + (width, _height) = drawer.textsize(line, font) + if height is None or height < _height: + height = _height + + _lines.append({ + "width": width, + "text": line + }) + + return (_lines, height) + + +width = 1920 +height = 1080 +# width = 800 +# height = 600 +bg_color_hex = "#242424" +bg_color = ImageColor.getrgb(bg_color_hex) + +base = Image.new('RGB', (width, height), color=bg_color) + +texts = [ + ("Version name:", "mei_101_001_0020_slate_NFX_v001"), + ("Date:", "2019-08-09"), + ("Shot Types:", "2d comp"), + # ("Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.") +] +text_widths_rel = (2, 8) +col_alignments = ("right", "left") +fonts = (("arial", 20), ("times", 26)) +font_colors = ("#999999", "#ffffff") + +table_color_hex = "#212121" +table_alter_color_hex = "#272727" + +drawer = ImageDraw.Draw(base) +table_d = TableDraw( + width, height, + 0.1, 0.1, 0.5, + col_fonts=fonts, + col_font_colors=font_colors, + rel_col_widths=text_widths_rel, + col_alignments=col_alignments, + bg_color=table_color_hex, + alter_bg_color=table_alter_color_hex, + pad_top=20, pad_bottom=20, pad_left=5, pad_right=5 +) + +table_d.add_texts(texts) +table_d.draw(drawer) + +image_path = r"C:\Users\iLLiCiT\Desktop\Prace\Pillow\image.jpg" +image = Image.open(image_path) +img_width, img_height = image.size + +rel_image_width = 0.3 +rel_image_pos_x = 0.65 +rel_image_pos_y = 0.1 +image_pos_x = int(width * rel_image_pos_x) +image_pos_y = int(width * rel_image_pos_y) + +new_width = int(width * rel_image_width) +new_height = int(new_width * img_height / img_width) +image = image.resize((new_width, new_height), Image.ANTIALIAS) + +# mask = Image.new("L", image.size, 255) + +base.paste(image, (image_pos_x, image_pos_y)) +base.save(r"C:\Users\iLLiCiT\Desktop\Prace\Pillow\test{}x{}.jpg".format( + width, height +)) +base.show() From 449fb26aa796681ed77ffb62260ff0dcebc666b4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Sun, 12 Jan 2020 23:14:40 +0100 Subject: [PATCH 04/42] added margins and modified style --- pype/scripts/slate/base.py | 117 +++++++++----------------- pype/scripts/slate/default_style.json | 44 ++++++++++ pype/scripts/slate/style_schema.json | 44 ++++++++++ 3 files changed, 130 insertions(+), 75 deletions(-) create mode 100644 pype/scripts/slate/default_style.json create mode 100644 pype/scripts/slate/style_schema.json diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index cd51c94aab..5ae7109ed4 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -3,6 +3,7 @@ sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor from uuid import uuid4 + class BaseObj: """Base Object for slates.""" @@ -11,7 +12,7 @@ class BaseObj: def __init__(self, parent, style={}, name=None): if not self.obj_type: - raise NotImplemented( + raise NotImplementedError( "Class don't have set object type <{}>".format( self.__class__.__name__ ) @@ -50,18 +51,10 @@ class BaseObj: "font-italic": False, "bg-color": None, "bg-alter-color": None, - "alignment-vertical": "left", #left, center, right - "alignment-horizontal": "top", #top, center, bottom + "alignment-vertical": "left", + "alignment-horizontal": "top", "padding": 0, - # "padding-left": 0, - # "padding-right": 0, - # "padding-top": 0, - # "padding-bottom": 0 "margin": 0, - # "margin-left": 0, - # "margin-right": 0, - # "margin-top": 0, - # "margin-bottom": 0 }, "layer": {}, "image": {}, @@ -105,8 +98,9 @@ class BaseObj: for key, value in self._style.items(): if key in style: - style[key] = value - style.update() + style[key].update(value) + else: + style[self.obj_type][key] = value if self.is_drawing: self.final_style = style @@ -134,6 +128,7 @@ class BaseObj: return output + @property def pos_x(self): return 0 @@ -191,8 +186,8 @@ class MainFrame(BaseObj): obj_type = "main_frame" available_parents = [None] - def __init__(self, width, height, style={}, parent=None, name=None): - super(MainFrame, self).__init__(parent, style, name) + def __init__(self, width, height, *args, **kwargs): + super(MainFrame, self).__init__(*args, **kwargs) self._width = width self._height = height self._is_drawing = False @@ -212,7 +207,7 @@ class MainFrame(BaseObj): def draw(self, path): self._is_drawing = True bg_color = self.style["bg-color"] - image = Image.new("RGB", (self.width, self.height))#, color=bg_color) + image = Image.new("RGB", (self.width, self.height), color=bg_color) for item in self.items.values(): item.draw(image) @@ -221,14 +216,14 @@ class MainFrame(BaseObj): self.reset() - class Layer(BaseObj): obj_type = "layer" available_parents = ["main_frame", "layer"] # Direction can be 0=horizontal/ 1=vertical - def __init__(self, *args, **kwargs): + def __init__(self, direction=0, *args, **kwargs): super(Layer, self).__init__(*args, **kwargs) + self._direction = direction @property def pos_x(self): @@ -248,15 +243,20 @@ class Layer(BaseObj): @property def direction(self): - direction = self.style.get("direction", 0) - if direction not in (0, 1): - raise Exception( + if self._direction not in (0, 1): + print( "Direction must be 0 or 1 (0 is Vertical / 1 is horizontal)!" ) - return direction + return 0 + return self._direction def item_pos_x(self, item_id): pos_x = self.pos_x + + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + + pos_x += margin_left if self.direction != 0: for id, item in self.items.items(): if id == item_id: @@ -265,12 +265,15 @@ class Layer(BaseObj): if item.obj_type != "image": pos_x += 1 - if pos_x != self.pos_x: - pos_x += 1 return pos_x def item_pos_y(self, item_id): pos_y = self.pos_y + + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + + pos_y += margin_top if self.direction != 1: for id, item in self.items.items(): if item_id == id: @@ -283,7 +286,11 @@ class Layer(BaseObj): @property def height(self): - height = 0 + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + margin_bottom = self.style.get("margin-bottom") or margin + + height = (margin_top + margin_bottom) for item in self.items.values(): if self.direction == 0: if height > item.height: @@ -294,6 +301,7 @@ class Layer(BaseObj): else: height += item.height + min_height = self.style.get("min-height") if min_height > height: return min_height @@ -301,7 +309,11 @@ class Layer(BaseObj): @property def width(self): - width = 0 + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + margin_right = self.style.get("margin-right") or margin + + width = (margin_left + margin_right) for item in self.items.values(): if self.direction == 0: if width > item.width: @@ -402,9 +414,9 @@ class BaseItem(BaseObj): margin_bottom = self.style.get("margin-bottom") or margin return ( - height + - margin_bottom + margin_top + - padding_top + padding_bottom + height + + margin_bottom + margin_top + + padding_top + padding_bottom ) def add_item(self, *args, **kwargs): @@ -417,6 +429,7 @@ class BaseItem(BaseObj): ) ) + class ItemImage(BaseItem): obj_type = "image" @@ -751,49 +764,3 @@ if __name__ == "__main__": main.draw(dst) print("*** Drawing done :)") - - -style_schema = { - "alignment": { - "description": "Alignment of item", - "type": "string", - "enum": ["left", "right", "center"], - "example": "left" - }, - "font-family": { - "description": "Font type", - "type": "string", - "example": "Arial" - }, - "font-size": { - "description": "Font size", - "type": "integer", - "example": 26 - }, - "font-color": { - "description": "Font color", - "type": "string", - "regex": ( - "^[#]{1}[a-fA-F0-9]{1}$" - " | ^[#]{1}[a-fA-F0-9]{3}$" - " | ^[#]{1}[a-fA-F0-9]{6}$" - ), - "example": "#ffffff" - }, - "font-bold": { - "description": "Font boldness", - "type": "boolean", - "example": True - }, - "bg-color": { - "description": "Background color, None means transparent", - "type": ["string", None], - "regex": ( - "^[#]{1}[a-fA-F0-9]{1}$" - " | ^[#]{1}[a-fA-F0-9]{3}$" - " | ^[#]{1}[a-fA-F0-9]{6}$" - ), - "example": "#333" - }, - "bg-alter-color": None, -} diff --git a/pype/scripts/slate/default_style.json b/pype/scripts/slate/default_style.json new file mode 100644 index 0000000000..c0f1006a4a --- /dev/null +++ b/pype/scripts/slate/default_style.json @@ -0,0 +1,44 @@ +{ + "*": { + "font-family": "arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#777777", + "bg-alter-color": "#666666", + "__alignment-vertical__values": ["left", "center", "right"], + "alignment-vertical": "left", + "__alignment-horizontal__values": ["top", "center", "bottom"], + "alignment-horizontal": "top", + "__padding__variants": [ + "padding-top", "padding-right", "padding-bottom", "padding-left" + ], + "padding": 0, + "__margin__variants": [ + "margin-top", "margin-right", "margin-bottom", "margin-left" + ], + "margin": 0, + }, + "layer": { + + }, + "image": { + + }, + "text": { + + }, + "table": { + + }, + "table-item": { + + }, + "table-item-col-0": { + + }, + "#MyName": { + + } +} diff --git a/pype/scripts/slate/style_schema.json b/pype/scripts/slate/style_schema.json new file mode 100644 index 0000000000..dab6151df5 --- /dev/null +++ b/pype/scripts/slate/style_schema.json @@ -0,0 +1,44 @@ +{ + "alignment": { + "description": "Alignment of item", + "type": "string", + "enum": ["left", "right", "center"], + "example": "left" + }, + "font-family": { + "description": "Font type", + "type": "string", + "example": "Arial" + }, + "font-size": { + "description": "Font size", + "type": "integer", + "example": 26 + }, + "font-color": { + "description": "Font color", + "type": "string", + "regex": ( + "^[#]{1}[a-fA-F0-9]{1}$" + " | ^[#]{1}[a-fA-F0-9]{3}$" + " | ^[#]{1}[a-fA-F0-9]{6}$" + ), + "example": "#ffffff" + }, + "font-bold": { + "description": "Font boldness", + "type": "boolean", + "example": True + }, + "bg-color": { + "description": "Background color, None means transparent", + "type": ["string", None], + "regex": ( + "^[#]{1}[a-fA-F0-9]{1}$" + " | ^[#]{1}[a-fA-F0-9]{3}$" + " | ^[#]{1}[a-fA-F0-9]{6}$" + ), + "example": "#333" + }, + "bg-alter-color": None, +} From 43fbdbf8fe95809b46f95d361cfd817364197a64 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jan 2020 12:45:46 +0100 Subject: [PATCH 05/42] added margins --- pype/scripts/slate/base.py | 395 +++++++++++++++++++------------------ 1 file changed, 205 insertions(+), 190 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 5ae7109ed4..bb5d127a2d 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -9,6 +9,15 @@ class BaseObj: obj_type = None available_parents = [] + all_style_keys = [ + "font-family", "font-size", "font-color", "font-bold", "font-italic", + "bg-color", "bg-alter-color", + "alignment-vertical", "alignment-horizontal", + "padding", "padding-left", "padding-right", + "padding-top", "padding-bottom", + "margin", "margin-left", "margin-right", + "margin-top", "margin-bottom", "width", "height" + ] def __init__(self, parent, style={}, name=None): if not self.obj_type: @@ -34,9 +43,7 @@ class BaseObj: self._style = style self.id = uuid4() - self.parent = parent self.name = name - self._style = style self.items = {} self.final_style = None @@ -51,18 +58,41 @@ class BaseObj: "font-italic": False, "bg-color": None, "bg-alter-color": None, - "alignment-vertical": "left", + "alignment-vertical": "right", "alignment-horizontal": "top", "padding": 0, "margin": 0, }, - "layer": {}, - "image": {}, - "text": {}, - "table": {}, - "table-item": {}, - "table-item-col-0": {}, - "#MyName": {} + "main_frame": { + "padding": 0, + "margin": 0 + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "image": { + "padding": 0, + "margin": 0 + }, + "text": { + # "alignment-vertical": "left", + # "alignment-horizontal": "top", + "padding": 0, + "margin": 0 + }, + "table": { + "padding": 0, + "margin": 0 + }, + "table-item": { + "padding": 0, + "margin": 0 + }, + "__not_implemented__": { + "table-item-col-0": {}, + "#MyName": {} + } } return default_style_v1 @@ -97,10 +127,18 @@ class BaseObj: style = self.main_style for key, value in self._style.items(): - if key in style: - style[key].update(value) - else: + if key in self.all_style_keys: + # TODO which variant is right? style[self.obj_type][key] = value + # style["*"][key] = value + else: + if key not in style: + style[key] = {} + + if isinstance(style[key], dict): + style[key].update(value) + else: + style[key] = value if self.is_drawing: self.final_style = style @@ -110,9 +148,8 @@ class BaseObj: @property def style(self): style = self.full_style - style.update(self._style) - base = style.get("*", style.get("global")) or {} + base = style.get("*") or {} obj_specific = style.get(self.obj_type) or {} name_specific = {} if self.name: @@ -128,49 +165,121 @@ class BaseObj: return output - @property - def pos_x(self): + def item_pos_x(self): return 0 @property - def pos_y(self): + def item_pos_y(self): return 0 @property - def pos_start(self): - return (self.pos_x, self.pos_y) + def content_pos_x(self): + pos_x = self.item_pos_x + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + margin_right = self.style.get("margin-right") or margin + return pos_x + margin_left @property - def pos_end(self): - pos_x, pos_y = self.pos_start + def content_pos_y(self): + pos_y = self.item_pos_y + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + margin_bottom = self.style.get("margin-bottom") or margin + return pos_y + margin_top + + @property + def value_pos_x(self): + pos_x = self.content_pos_x * 1 + padding = self.style["padding"] + padding_left = self.style.get("padding-left") or padding + pos_x += padding_left + + return pos_x + + @property + def value_pos_y(self): + pos_y = self.content_pos_y * 1 + padding = self.style["padding"] + padding_top = self.style.get("padding-top") or padding + pos_y += padding_top + + return pos_y + + @property + def value_pos_start(self): + return (self.value_pos_x, self.value_pos_y) + + @property + def value_pos_end(self): + pos_x, pos_y = self.value_pos_start pos_x += self.width pos_y += self.height return (pos_x, pos_y) @property - def max_width(self): - return self.style.get("max-width") or (self.width * 1) + def content_pos_start(self): + return (self.content_pos_x, self.content_pos_y) @property - def max_height(self): - return self.style.get("max-height") or (self.height * 1) + def content_pos_end(self): + pos_x, pos_y = self.content_pos_start + pos_x += self.content_width + pos_y += self.content_height + return (pos_x, pos_y) @property - def max_content_width(self): - width = self.max_width + def value_width(self): + raise NotImplementedError( + "Attribute is not implemented <{}>".format( + self.__class__.__name__ + ) + ) + + @property + def value_height(self): + raise NotImplementedError( + "Attribute is not implemented for <{}>".format( + self.__class__.__name__ + ) + ) + + @property + def content_width(self): + width = self.value_width padding = self.style["padding"] padding_left = self.style.get("padding-left") or padding padding_right = self.style.get("padding-right") or padding - return (width - (padding_left + padding_right)) + return width + padding_left + padding_right @property - def max_content_height(self): - height = self.max_height + def content_height(self): + height = self.value_height padding = self.style["padding"] padding_top = self.style.get("padding-top") or padding padding_bottom = self.style.get("padding-bottom") or padding - return (height - (padding_top + padding_bottom)) + return height + padding_top + padding_bottom + + @property + def width(self): + width = self.content_width + + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + margin_right = self.style.get("margin-right") or margin + + return width + margin_left + margin_right + + @property + def height(self): + height = self.content_height + + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + margin_bottom = self.style.get("margin-bottom") or margin + + return height + margin_bottom + margin_top def add_item(self, item): self.items[item.id] = item @@ -187,11 +296,26 @@ class MainFrame(BaseObj): available_parents = [None] def __init__(self, width, height, *args, **kwargs): + kwargs["parent"] = None super(MainFrame, self).__init__(*args, **kwargs) self._width = width self._height = height self._is_drawing = False + @property + def value_width(self): + width = 0 + for item in self.items.values(): + width += item.width + return width + + @property + def value_height(self): + height = 0 + for item in self.items.values(): + height += item.height + return height + @property def width(self): return self._width @@ -226,19 +350,19 @@ class Layer(BaseObj): self._direction = direction @property - def pos_x(self): + def item_pos_x(self): if self.parent.obj_type == self.obj_type: - pos_x = self.parent.item_pos_x(self.id) + pos_x = self.parent.child_pos_x(self.id) else: - pos_x = self.parent.pos_x + pos_x = self.parent.value_pos_x return pos_x @property - def pos_y(self): + def item_pos_y(self): if self.parent.obj_type == self.obj_type: - pos_y = self.parent.item_pos_y(self.id) + pos_y = self.parent.child_pos_y(self.id) else: - pos_y = self.parent.pos_y + pos_y = self.parent.value_pos_y return pos_y @property @@ -250,30 +374,21 @@ class Layer(BaseObj): return 0 return self._direction - def item_pos_x(self, item_id): - pos_x = self.pos_x + def child_pos_x(self, item_id): + pos_x = self.value_pos_x - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - - pos_x += margin_left if self.direction != 0: for id, item in self.items.items(): - if id == item_id: + if item_id == id: break - pos_x += item.width + pos_x += item.height if item.obj_type != "image": pos_x += 1 - return pos_x - def item_pos_y(self, item_id): - pos_y = self.pos_y + def child_pos_y(self, item_id): + pos_y = self.value_pos_y - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - - pos_y += margin_top if self.direction != 1: for id, item in self.items.items(): if item_id == id: @@ -281,21 +396,15 @@ class Layer(BaseObj): pos_y += item.height if item.obj_type != "image": pos_y += 1 - return pos_y @property - def height(self): - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - margin_bottom = self.style.get("margin-bottom") or margin - - height = (margin_top + margin_bottom) + def value_height(self): + height = 0 for item in self.items.values(): if self.direction == 0: if height > item.height: continue - # times 1 because won't get object pointer but number height = item.height * 1 else: @@ -308,24 +417,19 @@ class Layer(BaseObj): return height @property - def width(self): - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - margin_right = self.style.get("margin-right") or margin - - width = (margin_left + margin_right) + def value_width(self): + width = 0 for item in self.items.values(): if self.direction == 0: if width > item.width: continue - # times 1 because won't get object pointer but number width = item.width * 1 else: width += item.width min_width = self.style.get("min-width") - if min_width > width: + if min_width and min_width > width: return min_width return width @@ -344,80 +448,12 @@ class BaseItem(BaseObj): super(BaseItem, self).__init__(*args, **kwargs) @property - def pos_x(self): - return self.parent.item_pos_x(self.id) + def item_pos_x(self): + return self.parent.child_pos_x(self.id) @property - def pos_y(self): - return self.parent.item_pos_y(self.id) - - @property - def content_pos_x(self): - padding = self.style["padding"] - padding_left = self.style.get("padding-left") or padding - - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - - return self.pos_x + margin_left + padding_left - - @property - def content_pos_y(self): - padding = self.style["padding"] - padding_top = self.style.get("padding-top") or padding - - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - - return self.pos_y + margin_top + padding_top - - @property - def content_width(self): - raise NotImplementedError( - "Attribute is not implemented" - ) - - @property - def content_height(self): - raise NotImplementedError( - "Attribute is not implemented" - ) - - @property - def width(self): - width = self.content_width - - padding = self.style["padding"] - padding_left = self.style.get("padding-left") or padding - padding_right = self.style.get("padding-right") or padding - - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - margin_right = self.style.get("margin-right") or margin - - return ( - width + - margin_left + margin_right + - padding_left + padding_right - ) - - @property - def height(self): - height = self.content_height - - padding = self.style["padding"] - padding_top = self.style.get("padding-top") or padding - padding_bottom = self.style.get("padding-bottom") or padding - - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - margin_bottom = self.style.get("margin-bottom") or margin - - return ( - height - + margin_bottom + margin_top - + padding_top + padding_bottom - ) + def item_pos_y(self): + return self.parent.child_pos_y(self.id) def add_item(self, *args, **kwargs): raise Exception("Can't add item to an item, use layers instead.") @@ -440,20 +476,20 @@ class ItemImage(BaseItem): def draw(self, image, drawer): paste_image = Image.open(self.image_path) paste_image = paste_image.resize( - (self.content_width, self.content_height), + (self.value_width, self.value_height), Image.ANTIALIAS ) image.paste( paste_image, - (self.content_pos_x, self.content_pos_y) + (self.value_pos_x, self.value_pos_y) ) @property - def content_width(self): + def value_width(self): return self.style.get("width") @property - def content_height(self): + def value_height(self): return self.style.get("height") @@ -467,35 +503,27 @@ class ItemText(BaseItem): def draw(self, image, drawer): bg_color = self.style["bg-color"] if bg_color and bg_color.lower() != "transparent": - padding = self.style["padding"] - - padding_left = self.style.get("padding-left") or padding - padding_right = self.style.get("padding-right") or padding - padding_top = self.style.get("padding-top") or padding - padding_bottom = self.style.get("padding-bottom") or padding # TODO border outline styles drawer.rectangle( - (self.pos_start, self.pos_end), + (self.content_pos_start, self.content_pos_end), fill=bg_color, outline=None ) - text_pos_start = (self.content_pos_x, self.content_pos_y) - font_color = self.style["font-color"] font_family = self.style["font-family"] font_size = self.style["font-size"] font = ImageFont.truetype(font_family, font_size) drawer.text( - text_pos_start, + self.value_pos_start, self.value, font=font, fill=font_color ) @property - def content_width(self): + def value_width(self): font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -504,7 +532,7 @@ class ItemText(BaseItem): return width @property - def content_height(self): + def value_height(self): font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -571,15 +599,9 @@ class ItemTable(BaseItem): def draw(self, image, drawer): bg_color = self.style["bg-color"] if bg_color and bg_color.lower() != "transparent": - padding = self.style["padding"] - - padding_left = self.style.get("padding-left") or padding - padding_right = self.style.get("padding-right") or padding - padding_top = self.style.get("padding-top") or padding - padding_bottom = self.style.get("padding-bottom") or padding # TODO border outline styles drawer.rectangle( - (self.pos_start, self.pos_end), + (self.content_pos_start, self.content_pos_end), fill=bg_color, outline=None ) @@ -588,7 +610,7 @@ class ItemTable(BaseItem): value.draw(image, drawer) @property - def content_width(self): + def value_width(self): row_heights, col_widths = self.size_values width = 0 for _width in col_widths: @@ -599,7 +621,7 @@ class ItemTable(BaseItem): return width @property - def content_height(self): + def value_height(self): row_heights, col_widths = self.size_values height = 0 for _height in row_heights: @@ -611,8 +633,8 @@ class ItemTable(BaseItem): def pos_info_by_cord(self, cord_x, cord_y): row_heights, col_widths = self.size_values - pos_x = self.pos_x - pos_y = self.pos_y + pos_x = self.value_pos_x + pos_y = self.value_pos_y width = 0 height = 0 for idx, value in enumerate(col_widths): @@ -627,12 +649,6 @@ class ItemTable(BaseItem): break pos_y += value - padding = self.style["padding"] - - padding_left = self.style.get("padding-left") or padding - padding_top = self.style.get("padding-top") or padding - pos_x += padding_left - pos_y += padding_top return (pos_x, pos_y, width, height) def filled_style(self, cord_x, cord_y): @@ -652,7 +668,7 @@ class TableField(BaseItem): self.value = value @property - def content_width(self): + def value_width(self): font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -661,7 +677,7 @@ class TableField(BaseItem): return width @property - def content_height(self): + def value_height(self): font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -669,15 +685,19 @@ class TableField(BaseItem): height = font.getsize(self.value)[1] + 1 return height - def content_pos_x(self, pos_x, width): - padding = self.style["padding"] - padding_left = self.style.get("padding-left") or padding - return pos_x + padding_left + @property + def item_pos_x(self): + pos_x, pos_y, width, height = ( + self.parent.pos_info_by_cord(self.cord_x, self.cord_y) + ) + return pos_x - def content_pos_y(self, pos_y, height): - padding = self.style["padding"] - padding_top = self.style.get("padding-top") or padding - return pos_y + padding_top + @property + def item_pos_y(self): + pos_x, pos_y, width, height = ( + self.parent.pos_info_by_cord(self.cord_x, self.cord_y) + ) + return pos_y def draw(self, image, drawer): pos_x, pos_y, width, height = ( @@ -700,18 +720,13 @@ class TableField(BaseItem): outline=None ) - text_pos_start = ( - self.content_pos_x(pos_x, width), - self.content_pos_y(pos_y, height) - ) - font_color = self.style["font-color"] font_family = self.style["font-family"] font_size = self.style["font-size"] font = ImageFont.truetype(font_family, font_size) drawer.text( - text_pos_start, + self.value_pos_start, self.value, font=font, fill=font_color From 3ef67998f7b3c0c2e80453e0d3dc18bb116bd66c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 13 Jan 2020 16:34:15 +0100 Subject: [PATCH 06/42] added initial alignment --- pype/scripts/slate/base.py | 231 +++++++++++++++++++++++++++---------- 1 file changed, 173 insertions(+), 58 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index bb5d127a2d..84b8faf0da 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -12,7 +12,7 @@ class BaseObj: all_style_keys = [ "font-family", "font-size", "font-color", "font-bold", "font-italic", "bg-color", "bg-alter-color", - "alignment-vertical", "alignment-horizontal", + "alignment-horizontal", "alignment-vertical", "padding", "padding-left", "padding-right", "padding-top", "padding-bottom", "margin", "margin-left", "margin-right", @@ -56,10 +56,10 @@ class BaseObj: "font-color": "#ffffff", "font-bold": False, "font-italic": False, - "bg-color": None, - "bg-alter-color": None, - "alignment-vertical": "right", - "alignment-horizontal": "top", + "bg-color": "#0077ff", + "bg-alter-color": "#0055dd", + "alignment-horizontal": "center", + "alignment-vertical": "bottom", "padding": 0, "margin": 0, }, @@ -76,8 +76,6 @@ class BaseObj: "margin": 0 }, "text": { - # "alignment-vertical": "left", - # "alignment-horizontal": "top", "padding": 0, "margin": 0 }, @@ -86,6 +84,7 @@ class BaseObj: "margin": 0 }, "table-item": { + "alignment-horizontal": "right", "padding": 0, "margin": 0 }, @@ -178,15 +177,16 @@ class BaseObj: pos_x = self.item_pos_x margin = self.style["margin"] margin_left = self.style.get("margin-left") or margin - margin_right = self.style.get("margin-right") or margin - return pos_x + margin_left + + pos_x += margin_left + + return pos_x @property def content_pos_y(self): pos_y = self.item_pos_y margin = self.style["margin"] margin_top = self.style.get("margin-top") or margin - margin_bottom = self.style.get("margin-bottom") or margin return pos_y + margin_top @property @@ -281,6 +281,30 @@ class BaseObj: return height + margin_bottom + margin_top + # @property + # def max_width(self): + # return self.style.get("max-width") or self.width + # + # @property + # def max_height(self): + # return self.style.get("max-height") or self.height + # + # @property + # def max_content_width(self): + # width = self.max_width + # padding = self.style["padding"] + # padding_left = self.style.get("padding-left") or padding + # padding_right = self.style.get("padding-right") or padding + # return (width - (padding_left + padding_right)) + # + # @property + # def max_content_height(self): + # height = self.max_height + # padding = self.style["padding"] + # padding_top = self.style.get("padding-top") or padding + # padding_bottom = self.style.get("padding-bottom") or padding + # return (height - (padding_top + padding_bottom)) + def add_item(self, item): self.items[item.id] = item @@ -344,7 +368,7 @@ class Layer(BaseObj): obj_type = "layer" available_parents = ["main_frame", "layer"] - # Direction can be 0=horizontal/ 1=vertical + # Direction can be 0=vertical/ 1=horizontal def __init__(self, direction=0, *args, **kwargs): super(Layer, self).__init__(*args, **kwargs) self._direction = direction @@ -369,25 +393,51 @@ class Layer(BaseObj): def direction(self): if self._direction not in (0, 1): print( - "Direction must be 0 or 1 (0 is Vertical / 1 is horizontal)!" + "Direction must be 0 or 1 (0 is horizontal / 1 is vertical)!" ) return 0 return self._direction def child_pos_x(self, item_id): pos_x = self.value_pos_x + alignment_hor = self.style["alignment-horizontal"].lower() + + item = None + for id, _item in self.items.items(): + if item_id == id: + item = _item + break if self.direction != 0: - for id, item in self.items.items(): + for id, _item in self.items.items(): if item_id == id: break - pos_x += item.height - if item.obj_type != "image": + pos_x += _item.height + if _item.obj_type != "image": pos_x += 1 - return pos_x + + else: + if alignment_hor in ["center", "centre"]: + pos_x += (self.content_width - item.content_width) / 2 + + elif alignment_hor == "right": + pos_x += self.content_width - item.content_width + + else: + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + pos_x += margin_left + return int(pos_x) def child_pos_y(self, item_id): pos_y = self.value_pos_y + alignment_ver = self.style["alignment-horizontal"].lower() + + item = None + for id, _item in self.items.items(): + if item_id == id: + item = _item + break if self.direction != 1: for id, item in self.items.items(): @@ -396,7 +446,19 @@ class Layer(BaseObj): pos_y += item.height if item.obj_type != "image": pos_y += 1 - return pos_y + + else: + if alignment_ver in ["center", "centre"]: + pos_y += (self.content_height - item.content_height) / 2 + + elif alignment_ver == "bottom": + pos_y += self.content_height - item.content_height + + else: + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + pos_y += margin_top + return int(pos_y) @property def value_height(self): @@ -545,7 +607,7 @@ class ItemTable(BaseItem): obj_type = "table" - def __init__(self, values, *args, **kwargs): + def __init__(self, values, use_alternate_color=False, *args, **kwargs): super(ItemTable, self).__init__(*args, **kwargs) self.size_values = None self.values_by_cords = None @@ -553,22 +615,28 @@ class ItemTable(BaseItem): self.prepare_values(values) self.calculate_sizes() + self.use_alternate_color = use_alternate_color + def prepare_values(self, _values): values = [] values_by_cords = [] - for row_idx, row in enumerate(_values): - if len(values_by_cords) < row_idx + 1: - for i in range(len(values_by_cords), row_idx + 1): - values_by_cords.append([]) + row_count = 0 + col_count = 0 + for row in _values: + row_count += 1 + if len(row) > col_count: + col_count = len(row) - for col_idx, col in enumerate(_values[row_idx]): - if len(values_by_cords[row_idx]) < col_idx + 1: - for i in range(len(values_by_cords[row_idx]), col_idx + 1): - values_by_cords[row_idx].append("") - - if not col: + for row_idx in range(row_count): + values_by_cords.append([]) + for col_idx in range(col_count): + values_by_cords[row_idx].append([]) + if col_idx <= len(_values[row_idx]) - 1: + col = _values[row_idx][col_idx] + else: col = "" - col_item = TableField(row_idx, col_idx, col, self) + + col_item = TableField(row_idx, col_idx, col, parent=self) values_by_cords[row_idx][col_idx] = col_item values.append(col_item) @@ -631,7 +699,7 @@ class ItemTable(BaseItem): height -= 1 return height - def pos_info_by_cord(self, cord_x, cord_y): + def content_pos_info_by_cord(self, cord_x, cord_y): row_heights, col_widths = self.size_values pos_x = self.value_pos_x pos_y = self.value_pos_y @@ -669,6 +737,9 @@ class TableField(BaseItem): @property def value_width(self): + if not self.value: + return 0 + font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -678,6 +749,8 @@ class TableField(BaseItem): @property def value_height(self): + if not self.value: + return 0 font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -688,31 +761,68 @@ class TableField(BaseItem): @property def item_pos_x(self): pos_x, pos_y, width, height = ( - self.parent.pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) ) return pos_x @property def item_pos_y(self): pos_x, pos_y, width, height = ( - self.parent.pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) ) return pos_y + @property + def value_pos_x(self): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + ) + + alignment_hor = self.style["alignment-horizontal"].lower() + if alignment_hor in ["center", "centre"]: + pos_x += (width - self.value_width) / 2 + + elif alignment_hor == "right": + pos_x += width - self.value_width + + else: + padding = self.style["padding"] + padding_left = self.style.get("padding-left") or padding + pos_x += padding_left + + return int(pos_x) + + @property + def value_pos_y(self): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + ) + + alignment_ver = self.style["alignment-vertical"].lower() + if alignment_ver in ["center", "centre"]: + pos_y += (height - self.value_height) / 2 + + elif alignment_ver == "bottom": + pos_y += height - self.value_height + + else: + padding = self.style["padding"] + padding_top = self.style.get("padding-top") or padding + pos_y += padding_top + + return int(pos_y) + def draw(self, image, drawer): pos_x, pos_y, width, height = ( - self.parent.pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) ) pos_start = (pos_x, pos_y) pos_end = (pos_x + width, pos_y + height) bg_color = self.style["bg-color"] - if bg_color and bg_color.lower() != "transparent": - padding = self.style["padding"] + if self.parent.use_alternate_color and (self.cord_x % 2) == 1: + bg_color = self.style["bg-alter-color"] - padding_left = self.style.get("padding-left") or padding - padding_right = self.style.get("padding-right") or padding - padding_top = self.style.get("padding-top") or padding - padding_bottom = self.style.get("padding-bottom") or padding + if bg_color and bg_color.lower() != "transparent": # TODO border outline styles drawer.rectangle( (pos_start, pos_end), @@ -734,48 +844,53 @@ class TableField(BaseItem): if __name__ == "__main__": main_style = { - "bg-color": "#777777" + "bg-color": "#777777", + "margin": 0 } text_1_style = { - "padding": 10, + "padding": 0, "bg-color": "#00ff77" } text_2_style = { - "padding": 8, + "padding": 0, "bg-color": "#ff0066" } text_3_style = { "padding": 0, "bg-color": "#ff5500" } - main = MainFrame(1920, 1080, style=main_style) - layer = Layer(parent=main) - main.add_item(layer) - - text_1 = ItemText("Testing message 1", layer, text_1_style) - text_2 = ItemText("Testing message 2", layer, text_2_style) - text_3 = ItemText("Testing message 3", layer, text_3_style) - table_1_items = [["0", "Output text 1"], ["1", "Output text 2"], ["2", "Output text 3"]] - table_1_style = { - "padding": 8, - "bg-color": "#0077ff" - } - table_1 = ItemTable(table_1_items, layer, table_1_style) - image_1_style = { "width": 240, "height": 120, "bg-color": "#7733aa" } + table_1_style = { + "padding": 0, + "bg-color": "#0077ff" + } + + main = MainFrame(1920, 1080, style=main_style) + layer = Layer(parent=main) + main.add_item(layer) + + text_1 = ItemText("Testing message 1", layer, text_1_style) + text_2 = ItemText("Testing 2", layer, text_2_style) + text_3 = ItemText("Testing message 3", layer, text_3_style) + + table_1_items = [["0", "Output text 1", "ha"], ["1", "Output 2"], ["2", "Output text 3"]] + table_1 = ItemTable(table_1_items, True, parent=layer, style=table_1_style) + image_1_path = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\kitten.jpg" image_1 = ItemImage(image_1_path, layer, image_1_style) layer.add_item(text_1) layer.add_item(text_2) + layer.add_item(text_3) + layer.add_item(table_1) layer.add_item(image_1) - layer.add_item(text_3) - dst = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\test_output2.png" + + dst = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\test_output3.png" main.draw(dst) print("*** Drawing done :)") From 8899ce974fbfe91c69e87b5a390910a71e044a75 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jan 2020 14:33:59 +0100 Subject: [PATCH 07/42] added important imports --- pype/scripts/slate/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 84b8faf0da..33266bf62d 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -1,4 +1,9 @@ +import os import sys +import re +import copy +import json +from queue import Queue sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor from uuid import uuid4 From de2d156fc4955f1d4e246a5a0b74b4e5b263d58b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jan 2020 14:34:26 +0100 Subject: [PATCH 08/42] base object may have pos_x and pos_y --- pype/scripts/slate/base.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 33266bf62d..1aca5062be 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -24,7 +24,7 @@ class BaseObj: "margin-top", "margin-bottom", "width", "height" ] - def __init__(self, parent, style={}, name=None): + def __init__(self, parent, style={}, name=None, pos_x=None, pos_y=None): if not self.obj_type: raise NotImplementedError( "Class don't have set object type <{}>".format( @@ -50,7 +50,9 @@ class BaseObj: self.id = uuid4() self.name = name self.items = {} - self.final_style = None + + self._pos_x = pos_x or 0 + self._pos_y = pos_y or 0 @property def main_style(self): From 549898730a60c099b52f958fed5b24d242f59284 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jan 2020 14:35:51 +0100 Subject: [PATCH 09/42] style property use get_style_for_obj_type method --- pype/scripts/slate/base.py | 77 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 4 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 1aca5062be..6430e5d8f3 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -151,12 +151,12 @@ class BaseObj: return style - @property - def style(self): - style = self.full_style + def get_style_for_obj_type(self, obj_type, style=None): + if not style: + style = copy.deepcopy(self.full_style) base = style.get("*") or {} - obj_specific = style.get(self.obj_type) or {} + obj_specific = style.get(obj_type) or {} name_specific = {} if self.name: name = str(self.name) @@ -164,6 +164,67 @@ class BaseObj: name += "#" name_specific = style.get(name) or {} + + if obj_type == "table-item": + col_regex = r"table-item-col\[([\d\-, ]+)*\]" + row_regex = r"table-item-row\[([\d\-, ]+)*\]" + field_regex = ( + r"table-item-field\[(([ ]+)?\d+([ ]+)?:([ ]+)?\d+([ ]+)?)*\]" + ) + # STRICT field regex (not allowed spaces) + # fild_regex = r"table-item-field\[(\d+:\d+)*\]" + + def get_indexes_from_regex_match(result, field=False): + group = result.group(1) + indexes = [] + if field: + return [ + int(part.strip()) for part in group.strip().split(":") + ] + + parts = group.strip().split(",") + for part in parts: + part = part.strip() + if "-" not in part: + indexes.append(int(part)) + continue + + sub_parts = [ + int(sub.strip()) for sub in part.split("-") + ] + if len(sub_parts) != 2: + # TODO logging + print("invalid range '{}'".format(part)) + continue + + for idx in range(sub_parts[0], sub_parts[1]+1): + indexes.append(idx) + return indexes + + for key, value in style.items(): + if not key.startswith(obj_type): + continue + + result = re.search(col_regex, key) + if result: + indexes = get_indexes_from_regex_match(result) + if self.col_idx in indexes: + obj_specific.update(value) + continue + + result = re.search(row_regex, key) + if result: + indexes = get_indexes_from_regex_match(result) + if self.row_idx in indexes: + obj_specific.update(value) + continue + + result = re.search(field_regex, key) + if result: + col_idx, row_idx = get_indexes_from_regex_match(result) + if self.row_idx == col_idx and self.row_idx == row_idx: + obj_specific.update(value) + output = {} output.update(base) output.update(obj_specific) @@ -171,12 +232,20 @@ class BaseObj: return output + @property + def style(self): + return self.get_style_for_obj_type(self.obj_type) + @property def item_pos_x(self): + if self.parent.obj_type == "main_frame": + return self._pos_x return 0 @property def item_pos_y(self): + if self.parent.obj_type == "main_frame": + return self._pos_x return 0 @property From 7d40f571d58455d724a03f37fa190ed9ccc80184 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 14 Jan 2020 14:39:02 +0100 Subject: [PATCH 10/42] removed final_style, is_drawing and cleaned up little bit --- pype/scripts/slate/base.py | 54 +++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 6430e5d8f3..737d92de67 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -102,10 +102,6 @@ class BaseObj: } return default_style_v1 - @property - def is_drawing(self): - return self.parent.is_drawing - @property def height(self): raise NotImplementedError( @@ -124,9 +120,6 @@ class BaseObj: @property def full_style(self): - if self.is_drawing and self.final_style is not None: - return self.final_style - if self.parent is not None: style = dict(val for val in self.parent.full_style.items()) else: @@ -146,9 +139,6 @@ class BaseObj: else: style[key] = value - if self.is_drawing: - self.final_style = style - return style def get_style_for_obj_type(self, obj_type, style=None): @@ -385,7 +375,6 @@ class BaseObj: self.items[item.id] = item def reset(self): - self.final_style = None for item in self.items.values(): item.reset() @@ -395,12 +384,12 @@ class MainFrame(BaseObj): obj_type = "main_frame" available_parents = [None] - def __init__(self, width, height, *args, **kwargs): + def __init__(self, width, height, destination_path=None, *args, **kwargs): kwargs["parent"] = None super(MainFrame, self).__init__(*args, **kwargs) self._width = width self._height = height - self._is_drawing = False + self.dst_path = destination_path @property def value_width(self): @@ -424,19 +413,27 @@ class MainFrame(BaseObj): def height(self): return self._height - @property - def is_drawing(self): - return self._is_drawing + def draw(self, path=None): + if not path: + path = self.dst_path + + if not path: + raise TypeError(( + "draw() missing 1 required positional argument: 'path'" + " if 'destination_path is not specified'" + )) + + dir_path = os.path.dirname(path) + if not os.path.exists(dir_path): + os.makedirs(dir_path) - def draw(self, path): - self._is_drawing = True bg_color = self.style["bg-color"] - image = Image.new("RGB", (self.width, self.height), color=bg_color) + image = Image.new("RGB", (self.width(), self.height()), color=bg_color) + drawer = ImageDraw.Draw(image) for item in self.items.values(): - item.draw(image) + item.draw(image, drawer) image.save(path) - self._is_drawing = False self.reset() @@ -571,26 +568,27 @@ class Layer(BaseObj): return min_width return width - def draw(self, image, drawer=None): - if drawer is None: - drawer = ImageDraw.Draw(image) - + def draw(self, image, drawer): for item in self.items.values(): item.draw(image, drawer) class BaseItem(BaseObj): - available_parents = ["layer"] + available_parents = ["main_frame", "layer"] def __init__(self, *args, **kwargs): super(BaseItem, self).__init__(*args, **kwargs) @property def item_pos_x(self): + if self.parent.obj_type == "main_frame": + return self._pos_x return self.parent.child_pos_x(self.id) @property def item_pos_y(self): + if self.parent.obj_type == "main_frame": + return self._pos_y return self.parent.child_pos_y(self.id) def add_item(self, *args, **kwargs): @@ -795,10 +793,6 @@ class ItemTable(BaseItem): return (pos_x, pos_y, width, height) - def filled_style(self, cord_x, cord_y): - # TODO CHANGE STYLE BY CORDS - return self.style - class TableField(BaseItem): From 2b199538c461381d5939b8291d1fc1f1653988c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:26:47 +0100 Subject: [PATCH 11/42] return self._pos_x if parent in main_frame --- pype/scripts/slate/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 737d92de67..e985d9fd04 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -450,6 +450,8 @@ class Layer(BaseObj): def item_pos_x(self): if self.parent.obj_type == self.obj_type: pos_x = self.parent.child_pos_x(self.id) + elif self.parent.obj_type == "main_frame": + pos_x = self._pos_x else: pos_x = self.parent.value_pos_x return pos_x @@ -458,6 +460,8 @@ class Layer(BaseObj): def item_pos_y(self): if self.parent.obj_type == self.obj_type: pos_y = self.parent.child_pos_y(self.id) + elif self.parent.obj_type == "main_frame": + pos_y = self._pos_y else: pos_y = self.parent.value_pos_y return pos_y From ef609344dfddc2b0dd356362eac8c295b7c2bf5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:30:12 +0100 Subject: [PATCH 12/42] initial netflix version json --- pype/scripts/slate/netflix_v01.1.json | 141 ++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 pype/scripts/slate/netflix_v01.1.json diff --git a/pype/scripts/slate/netflix_v01.1.json b/pype/scripts/slate/netflix_v01.1.json new file mode 100644 index 0000000000..a2be6d13e0 --- /dev/null +++ b/pype/scripts/slate/netflix_v01.1.json @@ -0,0 +1,141 @@ +{ + "width": 1920, + "height": 1080, + "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v001.png", + "style": { + "*": { + "font-family": "arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top", + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": true + }, + "main_frame": { + "bg-color": "#252525" + }, + "table": { + "bg-color": "transparent" + }, + "table-item": { + "bg-color": "#272727", + "bg-alter-color": "#212121", + "font-color": "#ffffff", + "font-bold": true, + "font-italic": false, + "padding": 5, + "padding-bottom": 10, + "alignment-vertical": "top", + "alignment-horizontal": "left", + "word-wrap": true, + "ellide": true, + "max-lines": 3 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989" + }, + "table-item-col[1]": { + "font-size": 30, + "padding-left": 20 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "pos_x": 40, + "pos_y": 40, + "style": { + "width": 1094, + "height": 1000 + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "First Contact"] + ], + "style": { + "table-item-col[0]": { + "width": 300 + } + } + }, { + "type": "rectangle", + "style": { + "bg-color": "#d40914", + "width": 1094, + "height": 5, + "fill": true + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Version name:", "mei_101_001_0020_slate_NFX_v001"], + ["Date:", "2019-08-09"], + ["Shot Types:", "2d comp"], + ["Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production."] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 300 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 786 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "pos_x": 1174, + "pos_y": 40, + "style": { + "width": 733, + "height": 1000 + }, + "items": [{ + "type": "image", + "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", + "style": { + "width": 733, + "height": 414 + } + }, { + "type": "image", + "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", + "style": { + "width": 733, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Vendor:", "DAZZLE"], + ["Shot Name:", "SLATE_SIMPLE"], + ["Frames:", "0 - 1 (2)"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left" + }, + "table-item-col[1]": { + "alignment-horizontal": "right" + } + } + }] + }] +} From b872ee29919dd568e9ae935870e6ea6f708028b8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:30:54 +0100 Subject: [PATCH 13/42] added argument when processing field style --- pype/scripts/slate/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index e985d9fd04..467b504ca1 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -211,7 +211,9 @@ class BaseObj: result = re.search(field_regex, key) if result: - col_idx, row_idx = get_indexes_from_regex_match(result) + col_idx, row_idx = get_indexes_from_regex_match( + result, True + ) if self.row_idx == col_idx and self.row_idx == row_idx: obj_specific.update(value) From 8c5fd923fe5e4581e53febb930b7d1ee422b54a8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:32:17 +0100 Subject: [PATCH 14/42] created FontFactory --- pype/scripts/slate/base.py | 201 ++++++++++++++++++++++++++++++++----- 1 file changed, 174 insertions(+), 27 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 467b504ca1..37d59f13bf 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -918,32 +918,102 @@ class TableField(BaseItem): fill=font_color ) -if __name__ == "__main__": - main_style = { - "bg-color": "#777777", - "margin": 0 - } - text_1_style = { - "padding": 0, - "bg-color": "#00ff77" - } - text_2_style = { - "padding": 0, - "bg-color": "#ff0066" - } - text_3_style = { - "padding": 0, - "bg-color": "#ff5500" - } - image_1_style = { - "width": 240, - "height": 120, - "bg-color": "#7733aa" - } - table_1_style = { - "padding": 0, - "bg-color": "#0077ff" - } + +class FontFactory: + fonts = None + default = None + + @classmethod + def get_font(cls, family, font_size=None, italic=False, bold=False): + if cls.fonts is None: + cls.load_fonts() + + styles = [] + if bold: + styles.append("Bold") + + if italic: + styles.append("Italic") + + if not styles: + styles.append("Regular") + + style = " ".join(styles) + family = family.lower() + family_styles = cls.fonts.get(family) + if not family_styles: + return cls.default + + font = family_styles.get(style) + if font: + if font_size: + font = font.font_variant(size=font_size) + return font + + # Return first found + for font in family_styles: + if font_size: + font = font.font_variant(size=font_size) + return font + + return cls.default + + @classmethod + def load_fonts(cls): + + cls.default = ImageFont.load_default() + + available_font_ext = [".ttf", ".ttc"] + dirs = [] + if sys.platform == "win32": + # check the windows font repository + # NOTE: must use uppercase WINDIR, to work around bugs in + # 1.5.2's os.environ.get() + windir = os.environ.get("WINDIR") + if windir: + dirs.append(os.path.join(windir, "fonts")) + + elif sys.platform in ("linux", "linux2"): + lindirs = os.environ.get("XDG_DATA_DIRS", "") + if not lindirs: + # According to the freedesktop spec, XDG_DATA_DIRS should + # default to /usr/share + lindirs = "/usr/share" + dirs += [ + os.path.join(lindir, "fonts") for lindir in lindirs.split(":") + ] + + elif sys.platform == "darwin": + dirs += [ + "/Library/Fonts", + "/System/Library/Fonts", + os.path.expanduser("~/Library/Fonts") + ] + + available_fonts = collections.defaultdict(dict) + for directory in dirs: + for walkroot, walkdir, walkfilenames in os.walk(directory): + for walkfilename in walkfilenames: + ext = os.path.splitext(walkfilename)[1] + if ext.lower() not in available_font_ext: + continue + + fontpath = os.path.join(walkroot, walkfilename) + font_obj = ImageFont.truetype(fontpath) + family = font_obj.font.family.lower() + style = font_obj.font.style + available_fonts[family][style] = font_obj + + cls.fonts = available_fonts + + +def main_v01(): + main_style = {"bg-color": "#777777", "margin": 0} + text_1_style = {"padding": 0, "bg-color": "#00ff77"} + text_2_style = {"padding": 0, "bg-color": "#ff0066"} + text_3_style = {"padding": 0, "bg-color": "#ff5500"} + image_1_style = {"width": 240, "height": 120, "bg-color": "#7733aa"} + table_1_style = {"padding": 0, "bg-color": "#0077ff"} main = MainFrame(1920, 1080, style=main_style) layer = Layer(parent=main) @@ -969,4 +1039,81 @@ if __name__ == "__main__": dst = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\test_output3.png" main.draw(dst) - print("*** Drawing done :)") + +def main_v02(): + cur_folder = os.path.dirname(os.path.abspath(__file__)) + input_json = os.path.join(cur_folder, "netflix_v01.1.json") + with open(input_json) as json_file: + slate_data = json.load(json_file) + + width = slate_data["width"] + height = slate_data["height"] + style = slate_data.get("style") or {} + dst_path = slate_data.get("destination_path") + main = MainFrame(width, height, destination_path=dst_path, style=style) + + load_queue = Queue() + for item in slate_data["items"]: + load_queue.put((item, main)) + + all_objs = [] + while not load_queue.empty(): + item_data, parent = load_queue.get() + + item_type = item_data["type"].lower() + item_style = item_data.get("style", {}) + item_name = item_data.get("name") + + pos_x = None + pos_y = None + if parent.obj_type == "main_frame": + pos_x = item_data.get("pos_x", {}) + pos_y = item_data.get("pos_y", {}) + + kwargs = { + "parent": parent, + "style": item_style, + "pos_x": pos_x, + "pos_y": pos_y + } + + item_obj = None + if item_type == "layer": + direction = item_data.get("direction", 0) + item_obj = Layer(direction, **kwargs) + for item in item_data.get("items", []): + load_queue.put((item, item_obj)) + + elif item_type == "table": + use_alternate_color = item_data.get("use_alternate_color", False) + values = item_data.get("values") or [] + item_obj = ItemTable(values, use_alternate_color, **kwargs) + + elif item_type == "image": + path = item_data["path"] + item_obj = ItemImage(path, **kwargs) + + elif item_type == "rectangle": + item_obj = ItemRectangle(**kwargs) + + if not item_obj: + print( + "Slate item not implemented <{}> - skipping".format(item_type) + ) + continue + + all_objs.append(item_obj) + + parent.add_item(item_obj) + + main.draw() + # for item in all_objs: + # print(item.style.get("width"), item.style.get("height")) + # print(item.width, item.height) + # print(item.content_pos_x, item.content_pos_y) + # print(item.value_pos_x, item.value_pos_y) + + +if __name__ == "__main__": + main_v02() + print("*** Drawing is done") From 7a800142077bc2c580366344e3dd739070ae2185 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:37:32 +0100 Subject: [PATCH 15/42] all heights and widths are callable and not property --- pype/scripts/slate/base.py | 82 ++++++++++++++------------------------ 1 file changed, 30 insertions(+), 52 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 37d59f13bf..750061a844 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -102,7 +102,6 @@ class BaseObj: } return default_style_v1 - @property def height(self): raise NotImplementedError( "Attribute `height` is not implemented for <{}>".format( @@ -110,7 +109,6 @@ class BaseObj: ) ) - @property def width(self): raise NotImplementedError( "Attribute `width` is not implemented for <{}>".format( @@ -282,8 +280,8 @@ class BaseObj: @property def value_pos_end(self): pos_x, pos_y = self.value_pos_start - pos_x += self.width - pos_y += self.height + pos_x += self.width() + pos_y += self.height() return (pos_x, pos_y) @property @@ -293,11 +291,10 @@ class BaseObj: @property def content_pos_end(self): pos_x, pos_y = self.content_pos_start - pos_x += self.content_width - pos_y += self.content_height + pos_x += self.content_width() + pos_y += self.content_height() return (pos_x, pos_y) - @property def value_width(self): raise NotImplementedError( "Attribute is not implemented <{}>".format( @@ -305,7 +302,6 @@ class BaseObj: ) ) - @property def value_height(self): raise NotImplementedError( "Attribute is not implemented for <{}>".format( @@ -313,25 +309,22 @@ class BaseObj: ) ) - @property def content_width(self): - width = self.value_width + width = self.value_width() padding = self.style["padding"] padding_left = self.style.get("padding-left") or padding padding_right = self.style.get("padding-right") or padding return width + padding_left + padding_right - @property def content_height(self): - height = self.value_height + height = self.value_height() padding = self.style["padding"] padding_top = self.style.get("padding-top") or padding padding_bottom = self.style.get("padding-bottom") or padding return height + padding_top + padding_bottom - @property def width(self): - width = self.content_width + width = self.content_width() margin = self.style["margin"] margin_left = self.style.get("margin-left") or margin @@ -339,9 +332,8 @@ class BaseObj: return width + margin_left + margin_right - @property def height(self): - height = self.content_height + height = self.content_height() margin = self.style["margin"] margin_top = self.style.get("margin-top") or margin @@ -393,25 +385,21 @@ class MainFrame(BaseObj): self._height = height self.dst_path = destination_path - @property def value_width(self): width = 0 for item in self.items.values(): - width += item.width + width += item.width() return width - @property def value_height(self): height = 0 for item in self.items.values(): - height += item.height + height += item.height() return height - @property def width(self): return self._width - @property def height(self): return self._height @@ -491,16 +479,17 @@ class Layer(BaseObj): for id, _item in self.items.items(): if item_id == id: break - pos_x += _item.height + + pos_x += _item.height() if _item.obj_type != "image": pos_x += 1 else: if alignment_hor in ["center", "centre"]: - pos_x += (self.content_width - item.content_width) / 2 + pos_x += (self.content_width() - item.content_width()) / 2 elif alignment_hor == "right": - pos_x += self.content_width - item.content_width + pos_x += self.content_width() - item.content_width() else: margin = self.style["margin"] @@ -522,16 +511,16 @@ class Layer(BaseObj): for id, item in self.items.items(): if item_id == id: break - pos_y += item.height + pos_y += item.height() if item.obj_type != "image": pos_y += 1 else: if alignment_ver in ["center", "centre"]: - pos_y += (self.content_height - item.content_height) / 2 + pos_y += (self.content_height() - item.content_height()) / 2 elif alignment_ver == "bottom": - pos_y += self.content_height - item.content_height + pos_y += self.content_height() - item.content_height() else: margin = self.style["margin"] @@ -539,35 +528,33 @@ class Layer(BaseObj): pos_y += margin_top return int(pos_y) - @property def value_height(self): height = 0 for item in self.items.values(): if self.direction == 0: - if height > item.height: + if height > item.height(): continue # times 1 because won't get object pointer but number - height = item.height * 1 + height = item.height() else: - height += item.height + height += item.height() min_height = self.style.get("min-height") - if min_height > height: + if min_height and min_height > height: return min_height return height - @property def value_width(self): width = 0 for item in self.items.values(): if self.direction == 0: - if width > item.width: + if width > item.width(): continue # times 1 because won't get object pointer but number - width = item.width * 1 + width = item.width() else: - width += item.width + width += item.width() min_width = self.style.get("min-width") if min_width and min_width > width: @@ -626,11 +613,9 @@ class ItemImage(BaseItem): (self.value_pos_x, self.value_pos_y) ) - @property def value_width(self): return self.style.get("width") - @property def value_height(self): return self.style.get("height") @@ -664,7 +649,6 @@ class ItemText(BaseItem): fill=font_color ) - @property def value_width(self): font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -673,7 +657,6 @@ class ItemText(BaseItem): width = font.getsize(self.value)[0] return width - @property def value_height(self): font_family = self.style["font-family"] font_size = self.style["font-size"] @@ -733,12 +716,12 @@ class ItemTable(BaseItem): col_widths.append(0) _width = col_widths[col_idx] - item_width = col_item.width + item_width = col_item.width() if _width < item_width: col_widths[col_idx] = item_width _height = row_heights[row_idx] - item_height = col_item.height + item_height = col_item.height() if _height < item_height: row_heights[row_idx] = item_height @@ -757,7 +740,6 @@ class ItemTable(BaseItem): for value in self.values: value.draw(image, drawer) - @property def value_width(self): row_heights, col_widths = self.size_values width = 0 @@ -768,7 +750,6 @@ class ItemTable(BaseItem): width -= 1 return width - @property def value_height(self): row_heights, col_widths = self.size_values height = 0 @@ -811,7 +792,6 @@ class TableField(BaseItem): self.cord_y = cord_y self.value = value - @property def value_width(self): if not self.value: return 0 @@ -823,7 +803,6 @@ class TableField(BaseItem): width = font.getsize(self.value)[0] + 1 return width - @property def value_height(self): if not self.value: return 0 @@ -853,13 +832,12 @@ class TableField(BaseItem): pos_x, pos_y, width, height = ( self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) ) - alignment_hor = self.style["alignment-horizontal"].lower() if alignment_hor in ["center", "centre"]: - pos_x += (width - self.value_width) / 2 + pos_x += (width - self.value_width()) / 2 elif alignment_hor == "right": - pos_x += width - self.value_width + pos_x += width - self.value_width() else: padding = self.style["padding"] @@ -876,10 +854,10 @@ class TableField(BaseItem): alignment_ver = self.style["alignment-vertical"].lower() if alignment_ver in ["center", "centre"]: - pos_y += (height - self.value_height) / 2 + pos_y += (height - self.value_height()) / 2 elif alignment_ver == "bottom": - pos_y += height - self.value_height + pos_y += height - self.value_height() else: padding = self.style["padding"] From 25cb9b82896e343ca76793f47582fcfb7107fc54 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:38:30 +0100 Subject: [PATCH 16/42] added rectangle item --- pype/scripts/slate/base.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 750061a844..4029379655 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -618,6 +618,35 @@ class ItemImage(BaseItem): def value_height(self): return self.style.get("height") +class ItemRectangle(BaseItem): + obj_type = "rectangle" + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + fill = self.style.get("fill", False) + kwargs = {} + if fill: + kwargs["fill"] = bg_color + else: + kwargs["outline"] = bg_color + + start_pos_x = self.value_pos_x + start_pos_y = self.value_pos_y + end_pos_x = start_pos_x + self.value_width() + end_pos_y = start_pos_y + self.value_height() + drawer.rectangle( + ( + (start_pos_x, start_pos_y), + (end_pos_x, end_pos_y) + ), + **kwargs + ) + + def value_width(self): + return int(self.style["width"]) + + def value_height(self): + return int(self.style["height"]) class ItemText(BaseItem): From 184e1f17615fbfd3b35f3565ab3262b023cedcde Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:39:37 +0100 Subject: [PATCH 17/42] cleanup --- pype/scripts/slate/base.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 4029379655..7912b93026 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -21,7 +21,8 @@ class BaseObj: "padding", "padding-left", "padding-right", "padding-top", "padding-bottom", "margin", "margin-left", "margin-right", - "margin-top", "margin-bottom", "width", "height" + "margin-top", "margin-bottom", "width", "height", + "fill" ] def __init__(self, parent, style={}, name=None, pos_x=None, pos_y=None): @@ -229,13 +230,13 @@ class BaseObj: @property def item_pos_x(self): if self.parent.obj_type == "main_frame": - return self._pos_x + return int(self._pos_x) return 0 @property def item_pos_y(self): if self.parent.obj_type == "main_frame": - return self._pos_x + return int(self._pos_y) return 0 @property @@ -257,7 +258,7 @@ class BaseObj: @property def value_pos_x(self): - pos_x = self.content_pos_x * 1 + pos_x = int(self.content_pos_x) padding = self.style["padding"] padding_left = self.style.get("padding-left") or padding pos_x += padding_left @@ -266,7 +267,7 @@ class BaseObj: @property def value_pos_y(self): - pos_y = self.content_pos_y * 1 + pos_y = int(self.content_pos_y) padding = self.style["padding"] padding_top = self.style.get("padding-top") or padding pos_y += padding_top @@ -444,7 +445,7 @@ class Layer(BaseObj): pos_x = self._pos_x else: pos_x = self.parent.value_pos_x - return pos_x + return int(pos_x) @property def item_pos_y(self): @@ -454,7 +455,7 @@ class Layer(BaseObj): pos_y = self._pos_y else: pos_y = self.parent.value_pos_y - return pos_y + return int(pos_y) @property def direction(self): @@ -569,8 +570,6 @@ class Layer(BaseObj): class BaseItem(BaseObj): available_parents = ["main_frame", "layer"] - def __init__(self, *args, **kwargs): - super(BaseItem, self).__init__(*args, **kwargs) @property def item_pos_x(self): @@ -603,9 +602,9 @@ class ItemImage(BaseItem): self.image_path = image_path def draw(self, image, drawer): - paste_image = Image.open(self.image_path) - paste_image = paste_image.resize( - (self.value_width, self.value_height), + source_image = Image.open(os.path.normpath(self.image_path)) + paste_image = source_image.resize( + (self.value_width(), self.value_height()), Image.ANTIALIAS ) image.paste( @@ -614,10 +613,12 @@ class ItemImage(BaseItem): ) def value_width(self): - return self.style.get("width") + return int(self.style["width"]) def value_height(self): - return self.style.get("height") + return int(self.style["height"]) + + class ItemRectangle(BaseItem): obj_type = "rectangle" @@ -684,7 +685,7 @@ class ItemText(BaseItem): font = ImageFont.truetype(font_family, font_size) width = font.getsize(self.value)[0] - return width + return int(width) def value_height(self): font_family = self.style["font-family"] @@ -692,7 +693,7 @@ class ItemText(BaseItem): font = ImageFont.truetype(font_family, font_size) height = font.getsize(self.value)[1] - return height + return int(height) class ItemTable(BaseItem): From 9dd0079b28669454a1ff446c8459d47ae4a2866d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 12:41:08 +0100 Subject: [PATCH 18/42] use font factory for fonts --- pype/scripts/slate/base.py | 44 +++++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 7912b93026..1724984fce 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -670,8 +670,12 @@ class ItemText(BaseItem): font_color = self.style["font-color"] font_family = self.style["font-family"] font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) - font = ImageFont.truetype(font_family, font_size) + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) drawer.text( self.value_pos_start, self.value, @@ -682,16 +686,24 @@ class ItemText(BaseItem): def value_width(self): font_family = self.style["font-family"] font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) - font = ImageFont.truetype(font_family, font_size) + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) width = font.getsize(self.value)[0] return int(width) def value_height(self): font_family = self.style["font-family"] font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) - font = ImageFont.truetype(font_family, font_size) + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) height = font.getsize(self.value)[1] return int(height) @@ -828,20 +840,28 @@ class TableField(BaseItem): font_family = self.style["font-family"] font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) - font = ImageFont.truetype(font_family, font_size) - width = font.getsize(self.value)[0] + 1 - return width + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + width = font.getsize_multiline(self.value)[0] + 1 + return int(width) def value_height(self): if not self.value: return 0 font_family = self.style["font-family"] font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) - font = ImageFont.truetype(font_family, font_size) - height = font.getsize(self.value)[1] + 1 - return height + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + height = font.getsize_multiline(self.value)[1] + 1 + return int(height) @property def item_pos_x(self): @@ -917,8 +937,12 @@ class TableField(BaseItem): font_color = self.style["font-color"] font_family = self.style["font-family"] font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) - font = ImageFont.truetype(font_family, font_size) + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) drawer.text( self.value_pos_start, self.value, From 435713a076dd2e4d644a1b071472d6ce976df5eb Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 13:50:42 +0100 Subject: [PATCH 19/42] added recalculate method for word wrapping --- pype/scripts/slate/base.py | 102 +++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 1724984fce..c83394ce54 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -834,6 +834,108 @@ class TableField(BaseItem): self.cord_y = cord_y self.value = value + def recalculate_by_width(self, value, max_width): + if not value: + return "" + + word_wrap = self.style.get("word-wrap") + ellide = self.style.get("ellide") + max_lines = self.style.get("max-lines") + + if not ellide and not word_wrap: + # TODO logging + print(( + "Can't draw text because is too long with" + " `word-wrap` and `ellide` turned off" + )) + return "" + + elif ellide and not word_wrap: + max_lines = 1 + + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + + words = [word for word in value.split()] + words_len = len(words) + lines = [] + last_index = 0 + while True: + line = "" + for idx in range(last_index, words_len): + _word = words[idx] + _line = " ".join([line, _word]) + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + break + line = _line + last_index = idx + + if line: + lines.append(line) + + if last_index == words_len - 1: + break + + elif last_index == 0: + if ellide: + line = "" + for idx, char in enumerate(words[idx]): + _line = line + char + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = _line + + lines.append(line) + # TODO logging + print("Font size is too big.") + break + + output = "" + if not lines: + return output + + if max_lines and len(lines) > max_lines: + lines = [lines[idx] for idx in range(max_lines)] + if not ellide: + return "\n".join(lines) + + last_line = lines[-1] + last_line_width = font.getsize(last_line + self.ellide_text)[0] + if last_line_width <= max_width: + lines[-1] += self.ellide_text + return "\n".join([line for line in lines]) + + last_line_words = last_line.split() + if len(last_line_words) == 1: + if max_lines > 1: + lines[-1] = self.ellide_text + return "\n".join([line for line in lines]) + + _line = "" + for idx, char in enumerate(last_line): + _line = line + char + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = _line + lines[-1] = line + return "\n".join([line for line in lines]) + + return "\n".join([line for line in lines]) + + def value_width(self): if not self.value: return 0 From 96d22f8a9be81ceb7bcf094b77e7f67bf568a201 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 14:01:20 +0100 Subject: [PATCH 20/42] added default ellide text --- pype/scripts/slate/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index c83394ce54..b6e4bd2701 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -827,6 +827,7 @@ class TableField(BaseItem): obj_type = "table-item" available_parents = ["table"] + ellide_text = "..." def __init__(self, cord_x, cord_y, value, *args, **kwargs): super(TableField, self).__init__(*args, **kwargs) From 643ad0fcde771b3d86b6077633c43e93e9277bb9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 14:01:56 +0100 Subject: [PATCH 21/42] added import for font factory --- pype/scripts/slate/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index b6e4bd2701..1cea093929 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -3,6 +3,7 @@ import sys import re import copy import json +import collections from queue import Queue sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor From a4f2464583f30a7a9cb9f1ca94e0588c84006e51 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 14:03:10 +0100 Subject: [PATCH 22/42] replaced cord_x and cord_y with col_idx and row_idx --- pype/scripts/slate/base.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 1cea093929..5c81d11190 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -803,20 +803,20 @@ class ItemTable(BaseItem): height -= 1 return height - def content_pos_info_by_cord(self, cord_x, cord_y): + def content_pos_info_by_cord(self, row_idx, col_idx): row_heights, col_widths = self.size_values - pos_x = self.value_pos_x - pos_y = self.value_pos_y + pos_x = int(self.value_pos_x) + pos_y = int(self.value_pos_y) width = 0 height = 0 for idx, value in enumerate(col_widths): - if cord_y == idx: + if col_idx == idx: width = value break pos_x += value for idx, value in enumerate(row_heights): - if cord_x == idx: + if row_idx == idx: height = value break pos_y += value @@ -830,10 +830,11 @@ class TableField(BaseItem): available_parents = ["table"] ellide_text = "..." - def __init__(self, cord_x, cord_y, value, *args, **kwargs): + def __init__(self, row_idx, col_idx, value, *args, **kwargs): super(TableField, self).__init__(*args, **kwargs) - self.cord_x = cord_x - self.cord_y = cord_y + self.row_idx = row_idx + self.col_idx = col_idx + self.value = value def recalculate_by_width(self, value, max_width): @@ -970,21 +971,21 @@ class TableField(BaseItem): @property def item_pos_x(self): pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) return pos_x @property def item_pos_y(self): pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) return pos_y @property def value_pos_x(self): pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) alignment_hor = self.style["alignment-horizontal"].lower() if alignment_hor in ["center", "centre"]: @@ -1003,7 +1004,7 @@ class TableField(BaseItem): @property def value_pos_y(self): pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) alignment_ver = self.style["alignment-vertical"].lower() @@ -1022,12 +1023,12 @@ class TableField(BaseItem): def draw(self, image, drawer): pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.cord_x, self.cord_y) + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) ) pos_start = (pos_x, pos_y) pos_end = (pos_x + width, pos_y + height) bg_color = self.style["bg-color"] - if self.parent.use_alternate_color and (self.cord_x % 2) == 1: + if self.parent.use_alternate_color and (self.row_idx % 2) == 1: bg_color = self.style["bg-alter-color"] if bg_color and bg_color.lower() != "transparent": From 3526c14c44af95c45912a6d0b481259a3811d679 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 14:03:27 +0100 Subject: [PATCH 23/42] added few changes for width and height --- pype/scripts/slate/base.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 5c81d11190..40e507209f 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -835,6 +835,13 @@ class TableField(BaseItem): self.row_idx = row_idx self.col_idx = col_idx + self.orig_value = value + + max_width = self.style.get("max-width") + max_width = self.style.get("width") or max_width + if max_width: + value = self.recalculate_by_width(value, max_width) + self.value = value def recalculate_by_width(self, value, max_width): @@ -943,6 +950,10 @@ class TableField(BaseItem): if not self.value: return 0 + width = self.style.get("width") + if width: + return int(width) + font_family = self.style["font-family"] font_size = self.style["font-size"] font_bold = self.style.get("font-bold", False) @@ -952,11 +963,21 @@ class TableField(BaseItem): font_family, font_size, font_italic, font_bold ) width = font.getsize_multiline(self.value)[0] + 1 + + min_width = self.style.get("min-height") + if min_width and min_width > width: + width = min_width + return int(width) def value_height(self): if not self.value: return 0 + + height = self.style.get("height") + if height: + return int(height) + font_family = self.style["font-family"] font_size = self.style["font-size"] font_bold = self.style.get("font-bold", False) @@ -966,6 +987,11 @@ class TableField(BaseItem): font_family, font_size, font_italic, font_bold ) height = font.getsize_multiline(self.value)[1] + 1 + + min_height = self.style.get("min-height") + if min_height and min_height > height: + height = min_height + return int(height) @property From 83edea59360fca36240603c9076b529ddb22a3d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 14:03:50 +0100 Subject: [PATCH 24/42] remove set.json which is deprecated --- pype/scripts/slate/set.json | 59 ------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 pype/scripts/slate/set.json diff --git a/pype/scripts/slate/set.json b/pype/scripts/slate/set.json deleted file mode 100644 index e8bba8ac41..0000000000 --- a/pype/scripts/slate/set.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "width": 1920, - "height": 1020, - "bg-color": "#000000", - "__bg-color__": "For setting constant color of background. May cause issues if not set when there are not painted spaces.", - "bg-image": null, - "__bg-image__": "May be path to static image???", - "defaults": { - "font-family": "Arial", - "font-size": 26, - "font-color": "#ffffff", - "font-bold": false, - "bg-color": null, - "bg-alter-color": null, - "alignment": "left", - "__alignment[enum]__": ["left", "right", "center"] - }, - "items": [{ - "rel_pos_x": 0.1, - "rel_pos_y": 0.1, - "rel_width": 0.5, - "type": "table", - "col-format": [ - { - "font-size": 12, - "font-color": "#777777", - "alignment": "right" - }, - { - "alignment": "left" - } - ], - "rows": [ - [ - { - "name": "Version", - }, - { - "value": "mei_101_001_0020_slate_NFX_v001", - "font-bold": true - } - ], [ - { - "value": "Date:" - }, - { - "value": "{date}" - } - ] - ] - }, { - "rel_pos_x": 0.1, - "rel_pos_y": 0.1, - "rel_width": 0.5, - "rel_height": 0.5, - "type": "image", - } - ] -} From fc86e3fb19a5e0be7084e5d212aec4a9d8099f53 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 14:05:00 +0100 Subject: [PATCH 25/42] code cleanup --- pype/scripts/slate/base.py | 49 ++++---------------------------------- 1 file changed, 4 insertions(+), 45 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 40e507209f..6376554e2a 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -1169,41 +1169,7 @@ class FontFactory: cls.fonts = available_fonts - -def main_v01(): - main_style = {"bg-color": "#777777", "margin": 0} - text_1_style = {"padding": 0, "bg-color": "#00ff77"} - text_2_style = {"padding": 0, "bg-color": "#ff0066"} - text_3_style = {"padding": 0, "bg-color": "#ff5500"} - image_1_style = {"width": 240, "height": 120, "bg-color": "#7733aa"} - table_1_style = {"padding": 0, "bg-color": "#0077ff"} - - main = MainFrame(1920, 1080, style=main_style) - layer = Layer(parent=main) - main.add_item(layer) - - text_1 = ItemText("Testing message 1", layer, text_1_style) - text_2 = ItemText("Testing 2", layer, text_2_style) - text_3 = ItemText("Testing message 3", layer, text_3_style) - - table_1_items = [["0", "Output text 1", "ha"], ["1", "Output 2"], ["2", "Output text 3"]] - table_1 = ItemTable(table_1_items, True, parent=layer, style=table_1_style) - - image_1_path = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\kitten.jpg" - image_1 = ItemImage(image_1_path, layer, image_1_style) - - layer.add_item(text_1) - layer.add_item(text_2) - layer.add_item(text_3) - - layer.add_item(table_1) - layer.add_item(image_1) - - dst = r"C:\Users\jakub.trllo\Desktop\Tests\files\image\test_output3.png" - main.draw(dst) - - -def main_v02(): +def main(): cur_folder = os.path.dirname(os.path.abspath(__file__)) input_json = os.path.join(cur_folder, "netflix_v01.1.json") with open(input_json) as json_file: @@ -1219,7 +1185,6 @@ def main_v02(): for item in slate_data["items"]: load_queue.put((item, main)) - all_objs = [] while not load_queue.empty(): item_data, parent = load_queue.get() @@ -1260,23 +1225,17 @@ def main_v02(): item_obj = ItemRectangle(**kwargs) if not item_obj: + # TODO logging print( "Slate item not implemented <{}> - skipping".format(item_type) ) continue - all_objs.append(item_obj) - parent.add_item(item_obj) main.draw() - # for item in all_objs: - # print(item.style.get("width"), item.style.get("height")) - # print(item.width, item.height) - # print(item.content_pos_x, item.content_pos_y) - # print(item.value_pos_x, item.value_pos_y) + print("*** Drawing is done") if __name__ == "__main__": - main_v02() - print("*** Drawing is done") + main() From a2470faa64627036d3b8ae9f1d0e2b9b57b958bd Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 15:29:36 +0100 Subject: [PATCH 26/42] col row attribute name fix --- pype/scripts/slate/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 6376554e2a..457618327c 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -214,7 +214,7 @@ class BaseObj: col_idx, row_idx = get_indexes_from_regex_match( result, True ) - if self.row_idx == col_idx and self.row_idx == row_idx: + if self.col_idx == col_idx and self.row_idx == row_idx: obj_specific.update(value) output = {} From e9d2bab7910a1cca0553e218fb036356e54fdea7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 16:33:43 +0100 Subject: [PATCH 27/42] fixes for working slate --- pype/scripts/slate/base.py | 14 ++++--- pype/scripts/slate/netflix_v01.1.json | 57 +++++++++++++++++---------- 2 files changed, 45 insertions(+), 26 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 457618327c..bd090b2556 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -477,12 +477,12 @@ class Layer(BaseObj): item = _item break - if self.direction != 0: + if self.direction == 1: for id, _item in self.items.items(): if item_id == id: break - pos_x += _item.height() + pos_x += _item.width() if _item.obj_type != "image": pos_x += 1 @@ -497,6 +497,7 @@ class Layer(BaseObj): margin = self.style["margin"] margin_left = self.style.get("margin-left") or margin pos_x += margin_left + return int(pos_x) def child_pos_y(self, item_id): @@ -537,11 +538,11 @@ class Layer(BaseObj): if height > item.height(): continue # times 1 because won't get object pointer but number - height = item.height() - else: height += item.height() + else: + height = item.height() - + # TODO this is not right min_height = self.style.get("min-height") if min_height and min_height > height: return min_height @@ -550,7 +551,7 @@ class Layer(BaseObj): def value_width(self): width = 0 for item in self.items.values(): - if self.direction == 0: + if self.direction == 1: if width > item.width(): continue # times 1 because won't get object pointer but number @@ -1201,6 +1202,7 @@ def main(): kwargs = { "parent": parent, "style": item_style, + "name": item_name, "pos_x": pos_x, "pos_y": pos_y } diff --git a/pype/scripts/slate/netflix_v01.1.json b/pype/scripts/slate/netflix_v01.1.json index a2be6d13e0..91058ddb50 100644 --- a/pype/scripts/slate/netflix_v01.1.json +++ b/pype/scripts/slate/netflix_v01.1.json @@ -15,6 +15,10 @@ "padding": 0, "margin": 0 }, + "layer": { + "padding": 0, + "margin": 0 + }, "rectangle": { "padding": 0, "margin": 0, @@ -37,9 +41,9 @@ "padding-bottom": 10, "alignment-vertical": "top", "alignment-horizontal": "left", - "word-wrap": true, + "word-wrap": false, "ellide": true, - "max-lines": 3 + "max-lines": 1 }, "table-item-col[0]": { "font-size": 20, @@ -60,20 +64,30 @@ "height": 1000 }, "items": [{ - "type": "table", - "values": [ - ["Show:", "First Contact"] - ], + "type": "layer", + "direction": 1, "style": { + "layer": {"padding": 0}, "table-item-col[0]": { - "width": 300 + "width": 200 } - } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "First Contact"] + ] + }, { + "type": "table", + "values": [ + ["Submitting For:", "SAMPLE"] + ] + }] }, { "type": "rectangle", "style": { "bg-color": "#d40914", - "width": 1094, + "width": 1108, "height": 5, "fill": true } @@ -87,6 +101,11 @@ ["Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production."] ], "style": { + "table-item-field[1:3]": { + "word-wrap": true, + "ellide": true, + "max-lines": 4 + }, "table-item-col[0]": { "alignment-horizontal": "right", "width": 300 @@ -100,24 +119,20 @@ }, { "type": "layer", "name": "RightSide", - "pos_x": 1174, - "pos_y": 40, - "style": { - "width": 733, - "height": 1000 - }, + "pos_x": 1164, + "pos_y": 26, "items": [{ "type": "image", "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", "style": { - "width": 733, - "height": 414 + "width": 730, + "height": 412 } }, { "type": "image", "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", "style": { - "width": 733, + "width": 730, "height": 55 } }, { @@ -130,10 +145,12 @@ ], "style": { "table-item-col[0]": { - "alignment-horizontal": "left" + "alignment-horizontal": "left", + "width": 200 }, "table-item-col[1]": { - "alignment-horizontal": "right" + "alignment-horizontal": "right", + "width": 530 } } }] From db1e578cd80bdda0f89a11addf64bd748fd55057 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 18:48:38 +0100 Subject: [PATCH 28/42] fixed cases when value of tablefield does not need to ellide or word wrap --- pype/scripts/slate/base.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index bd090b2556..cb72d9af1c 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -853,6 +853,18 @@ class TableField(BaseItem): ellide = self.style.get("ellide") max_lines = self.style.get("max-lines") + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + val_width = font.getsize(value)[0] + if val_width <= max_width: + return value + if not ellide and not word_wrap: # TODO logging print(( From 9cc2c4d5844e4442256bee204286cb475ce5174e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 15 Jan 2020 18:52:43 +0100 Subject: [PATCH 29/42] few padding fixes --- pype/scripts/slate/base.py | 65 ++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index cb72d9af1c..a5404f318d 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -261,7 +261,10 @@ class BaseObj: def value_pos_x(self): pos_x = int(self.content_pos_x) padding = self.style["padding"] - padding_left = self.style.get("padding-left") or padding + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + pos_x += padding_left return pos_x @@ -270,7 +273,10 @@ class BaseObj: def value_pos_y(self): pos_y = int(self.content_pos_y) padding = self.style["padding"] - padding_top = self.style.get("padding-top") or padding + padding_top = self.style.get("padding-top") + if padding_top is None: + padding_top = padding + pos_y += padding_top return pos_y @@ -314,15 +320,27 @@ class BaseObj: def content_width(self): width = self.value_width() padding = self.style["padding"] - padding_left = self.style.get("padding-left") or padding - padding_right = self.style.get("padding-right") or padding + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + + padding_right = self.style.get("padding-right") + if padding_right is None: + padding_right = padding + return width + padding_left + padding_right def content_height(self): height = self.value_height() padding = self.style["padding"] - padding_top = self.style.get("padding-top") or padding - padding_bottom = self.style.get("padding-bottom") or padding + padding_top = self.style.get("padding-top") + if padding_top is None: + padding_top = padding + + padding_bottom = self.style.get("padding-bottom") + if padding_bottom is None: + padding_bottom = padding + return height + padding_top + padding_bottom def width(self): @@ -343,30 +361,6 @@ class BaseObj: return height + margin_bottom + margin_top - # @property - # def max_width(self): - # return self.style.get("max-width") or self.width - # - # @property - # def max_height(self): - # return self.style.get("max-height") or self.height - # - # @property - # def max_content_width(self): - # width = self.max_width - # padding = self.style["padding"] - # padding_left = self.style.get("padding-left") or padding - # padding_right = self.style.get("padding-right") or padding - # return (width - (padding_left + padding_right)) - # - # @property - # def max_content_height(self): - # height = self.max_height - # padding = self.style["padding"] - # padding_top = self.style.get("padding-top") or padding - # padding_bottom = self.style.get("padding-bottom") or padding - # return (height - (padding_top + padding_bottom)) - def add_item(self, item): self.items[item.id] = item @@ -846,6 +840,17 @@ class TableField(BaseItem): self.value = value def recalculate_by_width(self, value, max_width): + padding = self.style["padding"] + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + + padding_right = self.style.get("padding-right") + if padding_right is None: + padding_right = padding + + max_width -= (padding_left + padding_right) + if not value: return "" From ac202b9fbd3fbe1eb3cbf179a06655723f6dc0ca Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 14:28:20 +0100 Subject: [PATCH 30/42] implemented placeholder --- pype/scripts/slate/base.py | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index a5404f318d..4fc7cdb4d9 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -646,6 +646,53 @@ class ItemRectangle(BaseItem): return int(self.style["height"]) +class ItemPlaceHolder(BaseItem): + obj_type = "placeholder" + + def __init__(self, image_path, *args, **kwargs): + self.image_path = image_path + super(ItemPlaceHolder, self).__init__(*args, **kwargs) + + def fill_data_format(self): + if re.match(self.fill_data_regex, self.image_path): + self.image_path = self.image_path.format(**self.fill_data) + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + + kwargs = {} + if bg_color != "tranparent": + kwargs["fill"] = bg_color + + start_pos_x = self.value_pos_x + start_pos_y = self.value_pos_y + end_pos_x = start_pos_x + self.value_width() + end_pos_y = start_pos_y + self.value_height() + + drawer.rectangle( + ( + (start_pos_x, start_pos_y), + (end_pos_x, end_pos_y) + ), + **kwargs + ) + + def value_width(self): + return int(self.style["width"]) + + def value_height(self): + return int(self.style["height"]) + + def collect_data(self): + return { + "pos_x": self.value_pos_x, + "pos_y": self.value_pos_y, + "width": self.value_width(), + "height": self.value_height(), + "path": self.image_path + } + + class ItemText(BaseItem): obj_type = "text" From 4a8ab6fac8dbe9137304d1be036f8e01ebf32846 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 14:29:19 +0100 Subject: [PATCH 31/42] added collect_data and fill_data abilities --- pype/scripts/slate/base.py | 382 ++++++++++++------ .../{netflix_v01.1.json => netflix_v01.json} | 87 +++- pype/scripts/slate/netflix_v02.json | 213 ++++++++++ pype/scripts/slate/netflix_v03.json | 213 ++++++++++ 4 files changed, 758 insertions(+), 137 deletions(-) rename pype/scripts/slate/{netflix_v01.1.json => netflix_v01.json} (65%) create mode 100644 pype/scripts/slate/netflix_v02.json create mode 100644 pype/scripts/slate/netflix_v03.json diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py index 4fc7cdb4d9..1f90f92992 100644 --- a/pype/scripts/slate/base.py +++ b/pype/scripts/slate/base.py @@ -25,6 +25,7 @@ class BaseObj: "margin-top", "margin-bottom", "width", "height", "fill" ] + fill_data_regex = r"{[^}]+}" def __init__(self, parent, style={}, name=None, pos_x=None, pos_y=None): if not self.obj_type: @@ -56,6 +57,16 @@ class BaseObj: self._pos_x = pos_x or 0 self._pos_y = pos_y or 0 + if parent: + parent.add_item(self) + + def fill_data_format(self): + return + + @property + def fill_data(self): + return self.parent.fill_data + @property def main_style(self): default_style_v1 = { @@ -66,40 +77,43 @@ class BaseObj: "font-bold": False, "font-italic": False, "bg-color": "#0077ff", - "bg-alter-color": "#0055dd", - "alignment-horizontal": "center", - "alignment-vertical": "bottom", - "padding": 0, - "margin": 0, - }, - "main_frame": { - "padding": 0, - "margin": 0 + "alignment-horizontal": "left", + "alignment-vertical": "top" }, "layer": { "padding": 0, "margin": 0 }, - "image": { + "rectangle": { "padding": 0, - "margin": 0 + "margin": 0, + "bg-color": "#E9324B", + "fill": true }, - "text": { + "main_frame": { "padding": 0, - "margin": 0 + "margin": 0, + "bg-color": "#252525" }, "table": { "padding": 0, - "margin": 0 + "margin": 0, + "bg-color": "transparent" }, "table-item": { - "alignment-horizontal": "right", - "padding": 0, - "margin": 0 - }, - "__not_implemented__": { - "table-item-col-0": {}, - "#MyName": {} + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 } } return default_style_v1 @@ -118,6 +132,31 @@ class BaseObj: ) ) + def collect_data(self): + return None + + def find_item(self, obj_type=None, name=None): + obj_type_fits = False + name_fits = False + if obj_type is None or self.obj_type == obj_type: + obj_type_fits = True + + if name is None or self.name != name: + name_fits = True + + output = [] + if obj_type_fits and name_fits: + output.append(self) + + if not self.items: + return output + + for item in self.items.values(): + output.extend( + item.find_item(obj_type=obj_type, name=name) + ) + return output + @property def full_style(self): if self.parent is not None: @@ -151,10 +190,9 @@ class BaseObj: if self.name: name = str(self.name) if not name.startswith("#"): - name += "#" + name = "#" + name name_specific = style.get(name) or {} - if obj_type == "table-item": col_regex = r"table-item-col\[([\d\-, ]+)*\]" row_regex = r"table-item-row\[([\d\-, ]+)*\]" @@ -363,6 +401,8 @@ class BaseObj: def add_item(self, item): self.items[item.id] = item + item.fill_data_format() + def reset(self): for item in self.items.values(): @@ -374,12 +414,24 @@ class MainFrame(BaseObj): obj_type = "main_frame" available_parents = [None] - def __init__(self, width, height, destination_path=None, *args, **kwargs): + def __init__( + self, width, height, destination_path, fill_data={}, *args, **kwargs + ): kwargs["parent"] = None super(MainFrame, self).__init__(*args, **kwargs) self._width = width self._height = height self.dst_path = destination_path + self._fill_data = fill_data + self.fill_data_format() + + def fill_data_format(self): + if re.match(self.fill_data_regex, self.dst_path): + self.dst_path = self.dst_path.format(**self.fill_data) + + @property + def fill_data(self): + return self._fill_data def value_width(self): width = 0 @@ -400,16 +452,7 @@ class MainFrame(BaseObj): return self._height def draw(self, path=None): - if not path: - path = self.dst_path - - if not path: - raise TypeError(( - "draw() missing 1 required positional argument: 'path'" - " if 'destination_path is not specified'" - )) - - dir_path = os.path.dirname(path) + dir_path = os.path.dirname(self.dst_path) if not os.path.exists(dir_path): os.makedirs(dir_path) @@ -419,9 +462,24 @@ class MainFrame(BaseObj): for item in self.items.values(): item.draw(image, drawer) - image.save(path) + image.save(self.dst_path) self.reset() + def collect_data(self): + output = {} + output["width"] = self.width() + output["height"] = self.height() + output["slate_path"] = self.dst_path + + placeholders = self.find_item(obj_type="placeholder") + placeholders_data = [] + for placeholder in placeholders: + placeholders_data.append(placeholder.collect_data()) + + output["placeholders"] = placeholders_data + + return output + class Layer(BaseObj): obj_type = "layer" @@ -450,6 +508,7 @@ class Layer(BaseObj): pos_y = self._pos_y else: pos_y = self.parent.value_pos_y + return int(pos_y) @property @@ -477,7 +536,7 @@ class Layer(BaseObj): break pos_x += _item.width() - if _item.obj_type != "image": + if _item.obj_type not in ["image", "placeholder"]: pos_x += 1 else: @@ -509,7 +568,7 @@ class Layer(BaseObj): if item_id == id: break pos_y += item.height() - if item.obj_type != "image": + if item.obj_type not in ["image", "placeholder"]: pos_y += 1 else: @@ -519,22 +578,18 @@ class Layer(BaseObj): elif alignment_ver == "bottom": pos_y += self.content_height() - item.content_height() - else: - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - pos_y += margin_top return int(pos_y) def value_height(self): height = 0 for item in self.items.values(): - if self.direction == 0: + if self.direction == 1: if height > item.height(): continue # times 1 because won't get object pointer but number - height += item.height() - else: height = item.height() + else: + height += item.height() # TODO this is not right min_height = self.style.get("min-height") @@ -545,12 +600,14 @@ class Layer(BaseObj): def value_width(self): width = 0 for item in self.items.values(): - if self.direction == 1: + if self.direction == 0: if width > item.width(): continue # times 1 because won't get object pointer but number width = item.width() else: + if self.name == "LeftSide": + print(item, item.width()) width += item.width() min_width = self.style.get("min-width") @@ -566,7 +623,6 @@ class Layer(BaseObj): class BaseItem(BaseObj): available_parents = ["main_frame", "layer"] - @property def item_pos_x(self): if self.parent.obj_type == "main_frame": @@ -597,6 +653,10 @@ class ItemImage(BaseItem): super(ItemImage, self).__init__(*args, **kwargs) self.image_path = image_path + def fill_data_format(self): + if re.match(self.fill_data_regex, self.image_path): + self.image_path = self.image_path.format(**self.fill_data) + def draw(self, image, drawer): source_image = Image.open(os.path.normpath(self.image_path)) paste_image = source_image.resize( @@ -756,15 +816,25 @@ class ItemTable(BaseItem): obj_type = "table" def __init__(self, values, use_alternate_color=False, *args, **kwargs): + + self.values_by_cords = None + self.prepare_values(values) + super(ItemTable, self).__init__(*args, **kwargs) self.size_values = None - self.values_by_cords = None - - self.prepare_values(values) self.calculate_sizes() self.use_alternate_color = use_alternate_color + def add_item(self, item): + if item.obj_type == "table-item": + return + super(ItemTable, self).add_item(item) + + def fill_data_format(self): + for item in self.values: + item.fill_data_format() + def prepare_values(self, _values): values = [] values_by_cords = [] @@ -876,14 +946,6 @@ class TableField(BaseItem): super(TableField, self).__init__(*args, **kwargs) self.row_idx = row_idx self.col_idx = col_idx - - self.orig_value = value - - max_width = self.style.get("max-width") - max_width = self.style.get("width") or max_width - if max_width: - value = self.recalculate_by_width(value, max_width) - self.value = value def recalculate_by_width(self, value, max_width): @@ -928,24 +990,23 @@ class TableField(BaseItem): elif ellide and not word_wrap: max_lines = 1 - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - words = [word for word in value.split()] words_len = len(words) lines = [] - last_index = 0 + last_index = None while True: + start_index = 0 + if last_index is not None: + start_index = int(last_index) + 1 + line = "" - for idx in range(last_index, words_len): + for idx in range(start_index, words_len): _word = words[idx] - _line = " ".join([line, _word]) + connector = " " + if line == "": + connector = "" + + _line = connector.join([line, _word]) _line_width = font.getsize(_line)[0] if _line_width > max_width: break @@ -958,7 +1019,7 @@ class TableField(BaseItem): if last_index == words_len - 1: break - elif last_index == 0: + elif last_index is None: if ellide: line = "" for idx, char in enumerate(words[idx]): @@ -968,7 +1029,7 @@ class TableField(BaseItem): if idx == 0: line = _line break - line = _line + line = line + char lines.append(line) # TODO logging @@ -979,46 +1040,103 @@ class TableField(BaseItem): if not lines: return output - if max_lines and len(lines) > max_lines: - lines = [lines[idx] for idx in range(max_lines)] - if not ellide: - return "\n".join(lines) + over_max_lines = (max_lines and len(lines) > max_lines) + if not over_max_lines: + return "\n".join([line for line in lines]) - last_line = lines[-1] - last_line_width = font.getsize(last_line + self.ellide_text)[0] - if last_line_width <= max_width: - lines[-1] += self.ellide_text + lines = [lines[idx] for idx in range(max_lines)] + if not ellide: + return "\n".join(lines) + + last_line = lines[-1] + last_line_width = font.getsize(last_line + self.ellide_text)[0] + if last_line_width <= max_width: + lines[-1] += self.ellide_text + return "\n".join([line for line in lines]) + + last_line_words = last_line.split() + if len(last_line_words) == 1: + if max_lines > 1: + # TODO try previous line? + lines[-1] = self.ellide_text return "\n".join([line for line in lines]) - last_line_words = last_line.split() - if len(last_line_words) == 1: - if max_lines > 1: - lines[-1] = self.ellide_text - return "\n".join([line for line in lines]) + line = "" + for idx, word in enumerate(last_line_words): + _line = line + word + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = _line + lines[-1] = line - _line = "" - for idx, char in enumerate(last_line): - _line = line + char + self.ellide_text - _line_width = font.getsize(_line)[0] - if _line_width > max_width: - if idx == 0: - line = _line - break - line = _line - lines[-1] = line - return "\n".join([line for line in lines]) + return "\n".join([line for line in lines]) + + line = "" + for idx, _word in enumerate(last_line_words): + connector = " " + if line == "": + connector = "" + + _line = connector.join([line, _word + self.ellide_text]) + _line_width = font.getsize(_line)[0] + + if _line_width <= max_width: + line = connector.join([line, _word]) + continue + + if idx != 0: + line += self.ellide_text + break + + if max_lines != 1: + # TODO try previous line? + line = self.ellide_text + break + + for idx, char in enumerate(_word): + _line = line + char + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = line + char + break + + lines[-1] = line return "\n".join([line for line in lines]) + def fill_data_format(self): + value = self.value + if re.match(self.fill_data_regex, value): + value = value.format(**self.fill_data) + + self.orig_value = value + + max_width = self.style.get("max-width") + max_width = self.style.get("width") or max_width + if max_width: + value = self.recalculate_by_width(value, max_width) + + self.value = value + + def content_width(self): + width = self.style.get("width") + if width: + return int(width) + return super(TableField, self).content_width() + + def content_height(self): + return super(TableField, self).content_height() def value_width(self): if not self.value: return 0 - width = self.style.get("width") - if width: - return int(width) - font_family = self.style["font-family"] font_size = self.style["font-size"] font_bold = self.style.get("font-bold", False) @@ -1087,7 +1205,10 @@ class TableField(BaseItem): else: padding = self.style["padding"] - padding_left = self.style.get("padding-left") or padding + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + pos_x += padding_left return int(pos_x) @@ -1107,7 +1228,10 @@ class TableField(BaseItem): else: padding = self.style["padding"] - padding_top = self.style.get("padding-top") or padding + padding_top = self.style.get("padding-top") + if padding_top is None: + padding_top = padding + pos_y += padding_top return int(pos_y) @@ -1139,11 +1263,17 @@ class TableField(BaseItem): font = FontFactory.get_font( font_family, font_size, font_italic, font_bold ) - drawer.text( + + alignment_hor = self.style["alignment-horizontal"].lower() + if alignment_hor == "centre": + alignment_hor = "center" + + drawer.multiline_text( self.value_pos_start, self.value, font=font, - fill=font_color + fill=font_color, + align=alignment_hor ) @@ -1236,15 +1366,36 @@ class FontFactory: def main(): cur_folder = os.path.dirname(os.path.abspath(__file__)) - input_json = os.path.join(cur_folder, "netflix_v01.1.json") + # input_json = os.path.join(cur_folder, "netflix_v01.json") + # input_json = os.path.join(cur_folder, "netflix_v02.json") + input_json = os.path.join(cur_folder, "netflix_v03.json") with open(input_json) as json_file: slate_data = json.load(json_file) + fill_data = { + "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v03.png", + "project": { + "name": "Project name" + }, + "intent": "WIP", + "version_name": "mei_101_001_0020_slate_NFX_v001", + "date": "2019-08-09", + "shot_type": "2d comp", + "submission_note": "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.", + "thumbnail_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", + "color_bar_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", + "vendor": "DAZZLE", + "shot_name": "SLATE_SIMPLE", + "frame_start": 1001, + "frame_end": 1004, + "duration": 3 + } width = slate_data["width"] height = slate_data["height"] + dst_path = slate_data["destination_path"] style = slate_data.get("style") or {} - dst_path = slate_data.get("destination_path") - main = MainFrame(width, height, destination_path=dst_path, style=style) + + main = MainFrame(width, height, dst_path, fill_data, style=style) load_queue = Queue() for item in slate_data["items"]: @@ -1271,7 +1422,6 @@ def main(): "pos_y": pos_y } - item_obj = None if item_type == "layer": direction = item_data.get("direction", 0) item_obj = Layer(direction, **kwargs) @@ -1281,26 +1431,28 @@ def main(): elif item_type == "table": use_alternate_color = item_data.get("use_alternate_color", False) values = item_data.get("values") or [] - item_obj = ItemTable(values, use_alternate_color, **kwargs) + ItemTable(values, use_alternate_color, **kwargs) elif item_type == "image": path = item_data["path"] - item_obj = ItemImage(path, **kwargs) + ItemImage(path, **kwargs) elif item_type == "rectangle": - item_obj = ItemRectangle(**kwargs) + ItemRectangle(**kwargs) - if not item_obj: + elif item_type == "placeholder": + path = item_data["path"] + ItemPlaceHolder(path, **kwargs) + + else: # TODO logging print( "Slate item not implemented <{}> - skipping".format(item_type) ) - continue - - parent.add_item(item_obj) main.draw() - print("*** Drawing is done") + print(main.collect_data()) + print("*** Finished") if __name__ == "__main__": diff --git a/pype/scripts/slate/netflix_v01.1.json b/pype/scripts/slate/netflix_v01.json similarity index 65% rename from pype/scripts/slate/netflix_v01.1.json rename to pype/scripts/slate/netflix_v01.json index 91058ddb50..9a5974839d 100644 --- a/pype/scripts/slate/netflix_v01.1.json +++ b/pype/scripts/slate/netflix_v01.json @@ -1,7 +1,7 @@ { "width": 1920, "height": 1080, - "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v001.png", + "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v01.png", "style": { "*": { "font-family": "arial", @@ -32,33 +32,40 @@ "bg-color": "transparent" }, "table-item": { - "bg-color": "#272727", - "bg-alter-color": "#212121", - "font-color": "#ffffff", - "font-bold": true, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, "font-italic": false, "padding": 5, "padding-bottom": 10, - "alignment-vertical": "top", "alignment-horizontal": "left", + "alignment-vertical": "top", "word-wrap": false, "ellide": true, "max-lines": 1 }, "table-item-col[0]": { "font-size": 20, - "font-color": "#898989" + "font-color": "#898989", + "font-bold": true, + "ellide": false, + "word-wrap": true, + "max-lines": null }, "table-item-col[1]": { - "font-size": 30, - "padding-left": 20 + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" } }, "items": [{ "type": "layer", "name": "LeftSide", - "pos_x": 40, - "pos_y": 40, + "pos_x": 27, + "pos_y": 27, "style": { "width": 1094, "height": 1000 @@ -67,26 +74,52 @@ "type": "layer", "direction": 1, "style": { - "layer": {"padding": 0}, + "table-item": { + "bg-color": "transparent" + }, "table-item-col[0]": { - "width": 200 + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": true, + "font-size": 40 } }, "items": [{ "type": "table", "values": [ ["Show:", "First Contact"] - ] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[1:0]": { + "width": 580 + } + } }, { "type": "table", "values": [ ["Submitting For:", "SAMPLE"] - ] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[1:0]": { + "width": 218, + "alignment-horizontal": "right" + } + } }] }, { "type": "rectangle", "style": { - "bg-color": "#d40914", + "bg-color": "#bc1015", "width": 1108, "height": 5, "fill": true @@ -101,18 +134,24 @@ ["Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production."] ], "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[1:0]": { + "font-bold": true + }, "table-item-field[1:3]": { "word-wrap": true, "ellide": true, - "max-lines": 4 + "max-lines": 2 }, "table-item-col[0]": { "alignment-horizontal": "right", - "width": 300 + "width": 150 }, "table-item-col[1]": { "alignment-horizontal": "left", - "width": 786 + "width": 958 } } }] @@ -122,15 +161,18 @@ "pos_x": 1164, "pos_y": 26, "items": [{ - "type": "image", + "type": "placeholder", + "name": "thumbnail", "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", "style": { "width": 730, "height": 412 } }, { - "type": "image", + "type": "placeholder", + "name": "colorbar", "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", + "return_data": true, "style": { "width": 730, "height": 55 @@ -150,7 +192,8 @@ }, "table-item-col[1]": { "alignment-horizontal": "right", - "width": 530 + "width": 530, + "font-size": 30 } } }] diff --git a/pype/scripts/slate/netflix_v02.json b/pype/scripts/slate/netflix_v02.json new file mode 100644 index 0000000000..f373ed8134 --- /dev/null +++ b/pype/scripts/slate/netflix_v02.json @@ -0,0 +1,213 @@ +{ + "width": 1920, + "height": 1080, + "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v02.png", + "style": { + "*": { + "font-family": "arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top" + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": true + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "font-bold": true, + "ellide": false, + "word-wrap": true, + "max-lines": null + }, + "table-item-col[1]": { + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" + } + }, + "items": [{ + "type": "layer", + "direction": 1, + "name": "MainLayer", + "style": { + "#MainLayer": { + "width": 1094, + "height": 1000, + "margin": 25, + "padding": 0 + }, + "#LeftSide": { + "margin-right": 25 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "items": [{ + "type": "layer", + "direction": 1, + "style": { + "table-item": { + "bg-color": "transparent", + "padding-bottom": 20 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": true, + "font-size": 40 + } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "First Contact"] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[1:0]": { + "width": 580 + } + } + }, { + "type": "table", + "values": [ + ["Submitting For:", "SAMPLE"] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[1:0]": { + "width": 218, + "alignment-horizontal": "right" + } + } + }] + }, { + "type": "rectangle", + "style": { + "bg-color": "#bc1015", + "width": 1108, + "height": 5, + "fill": true + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Version name:", "mei_101_001_0020_slate_NFX_v001"], + ["Date:", "2019-08-09"], + ["Shot Types:", "2d comp"], + ["Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production."] + ], + "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[1:0]": { + "font-bold": true + }, + "table-item-field[1:3]": { + "word-wrap": true, + "ellide": true, + "max-lines": 2 + }, + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 150 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 958 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "items": [{ + "type": "placeholder", + "name": "thumbnail", + "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", + "style": { + "width": 730, + "height": 412 + } + }, { + "type": "placeholder", + "name": "colorbar", + "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", + "return_data": true, + "style": { + "width": 730, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Vendor:", "DAZZLE"], + ["Shot Name:", "SLATE_SIMPLE"], + ["Frames:", "0 - 1 (2)"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left", + "width": 200 + }, + "table-item-col[1]": { + "alignment-horizontal": "right", + "width": 530, + "font-size": 30 + } + } + }] + }] + }] +} diff --git a/pype/scripts/slate/netflix_v03.json b/pype/scripts/slate/netflix_v03.json new file mode 100644 index 0000000000..975cb60133 --- /dev/null +++ b/pype/scripts/slate/netflix_v03.json @@ -0,0 +1,213 @@ +{ + "width": 1920, + "height": 1080, + "destination_path": "{destination_path}", + "style": { + "*": { + "font-family": "arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top" + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": true + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "font-bold": true, + "ellide": false, + "word-wrap": true, + "max-lines": null + }, + "table-item-col[1]": { + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" + } + }, + "items": [{ + "type": "layer", + "direction": 1, + "name": "MainLayer", + "style": { + "#MainLayer": { + "width": 1094, + "height": 1000, + "margin": 25, + "padding": 0 + }, + "#LeftSide": { + "margin-right": 25 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "items": [{ + "type": "layer", + "direction": 1, + "style": { + "table-item": { + "bg-color": "transparent", + "padding-bottom": 20 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": true, + "font-size": 40 + } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "{project[name]}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[1:0]": { + "width": 580 + } + } + }, { + "type": "table", + "values": [ + ["Submitting For:", "{intent}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[1:0]": { + "width": 218, + "alignment-horizontal": "right" + } + } + }] + }, { + "type": "rectangle", + "style": { + "bg-color": "#bc1015", + "width": 1108, + "height": 5, + "fill": true + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Version name:", "{version_name}"], + ["Date:", "{date}"], + ["Shot Types:", "{shot_type}"], + ["Submission Note:", "{submission_note}"] + ], + "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[1:0]": { + "font-bold": true + }, + "table-item-field[1:3]": { + "word-wrap": true, + "ellide": true, + "max-lines": 4 + }, + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 150 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 958 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "items": [{ + "type": "placeholder", + "name": "thumbnail", + "path": "{thumbnail_path}", + "style": { + "width": 730, + "height": 412 + } + }, { + "type": "placeholder", + "name": "colorbar", + "path": "{color_bar_path}", + "return_data": true, + "style": { + "width": 730, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Vendor:", "{vendor}"], + ["Shot Name:", "{shot_name}"], + ["Frames:", "{frame_start} - {frame_end} ({duration})"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left", + "width": 200 + }, + "table-item-col[1]": { + "alignment-horizontal": "right", + "width": 530, + "font-size": 30 + } + } + }] + }] + }] +} From 6ef5853b65cb86cbdd94ae8d713ddb1fac753bdf Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 16:33:15 +0100 Subject: [PATCH 32/42] reorganized slates a little bit --- pype/scripts/slate/base.py | 1459 ----------------- pype/scripts/slate/default_style.json | 44 - pype/scripts/slate/lib.py | 114 ++ pype/scripts/slate/slate.py | 355 ---- pype/scripts/slate/slate_base/__init__.py | 0 pype/scripts/slate/slate_base/base.py | 373 +++++ .../slate/slate_base/default_style.json | 58 + pype/scripts/slate/slate_base/font_factory.py | 93 ++ pype/scripts/slate/slate_base/items.py | 666 ++++++++ pype/scripts/slate/slate_base/layer.py | 139 ++ pype/scripts/slate/slate_base/main_frame.py | 77 + pype/scripts/slate/style_schema.json | 44 - 12 files changed, 1520 insertions(+), 1902 deletions(-) delete mode 100644 pype/scripts/slate/base.py delete mode 100644 pype/scripts/slate/default_style.json create mode 100644 pype/scripts/slate/lib.py delete mode 100644 pype/scripts/slate/slate.py create mode 100644 pype/scripts/slate/slate_base/__init__.py create mode 100644 pype/scripts/slate/slate_base/base.py create mode 100644 pype/scripts/slate/slate_base/default_style.json create mode 100644 pype/scripts/slate/slate_base/font_factory.py create mode 100644 pype/scripts/slate/slate_base/items.py create mode 100644 pype/scripts/slate/slate_base/layer.py create mode 100644 pype/scripts/slate/slate_base/main_frame.py delete mode 100644 pype/scripts/slate/style_schema.json diff --git a/pype/scripts/slate/base.py b/pype/scripts/slate/base.py deleted file mode 100644 index 1f90f92992..0000000000 --- a/pype/scripts/slate/base.py +++ /dev/null @@ -1,1459 +0,0 @@ -import os -import sys -import re -import copy -import json -import collections -from queue import Queue -sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") -from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor -from uuid import uuid4 - - -class BaseObj: - """Base Object for slates.""" - - obj_type = None - available_parents = [] - all_style_keys = [ - "font-family", "font-size", "font-color", "font-bold", "font-italic", - "bg-color", "bg-alter-color", - "alignment-horizontal", "alignment-vertical", - "padding", "padding-left", "padding-right", - "padding-top", "padding-bottom", - "margin", "margin-left", "margin-right", - "margin-top", "margin-bottom", "width", "height", - "fill" - ] - fill_data_regex = r"{[^}]+}" - - def __init__(self, parent, style={}, name=None, pos_x=None, pos_y=None): - if not self.obj_type: - raise NotImplementedError( - "Class don't have set object type <{}>".format( - self.__class__.__name__ - ) - ) - - parent_obj_type = None - if parent: - parent_obj_type = parent.obj_type - - if parent_obj_type not in self.available_parents: - expected_parents = ", ".join(self.available_parents) - raise Exception(( - "Invalid parent <{}> for <{}>. Expected <{}>" - ).format( - parent.__class__.__name__, self.obj_type, expected_parents - )) - - self.parent = parent - self._style = style - - self.id = uuid4() - self.name = name - self.items = {} - - self._pos_x = pos_x or 0 - self._pos_y = pos_y or 0 - - if parent: - parent.add_item(self) - - def fill_data_format(self): - return - - @property - def fill_data(self): - return self.parent.fill_data - - @property - def main_style(self): - default_style_v1 = { - "*": { - "font-family": "arial", - "font-size": 26, - "font-color": "#ffffff", - "font-bold": False, - "font-italic": False, - "bg-color": "#0077ff", - "alignment-horizontal": "left", - "alignment-vertical": "top" - }, - "layer": { - "padding": 0, - "margin": 0 - }, - "rectangle": { - "padding": 0, - "margin": 0, - "bg-color": "#E9324B", - "fill": true - }, - "main_frame": { - "padding": 0, - "margin": 0, - "bg-color": "#252525" - }, - "table": { - "padding": 0, - "margin": 0, - "bg-color": "transparent" - }, - "table-item": { - "padding": 5, - "padding-bottom": 10, - "margin": 0, - "bg-color": "#212121", - "bg-alter-color": "#272727", - "font-color": "#dcdcdc", - "font-bold": false, - "font-italic": false, - "alignment-horizontal": "left", - "alignment-vertical": "top", - "word-wrap": false, - "ellide": true, - "max-lines": 1 - } - } - return default_style_v1 - - def height(self): - raise NotImplementedError( - "Attribute `height` is not implemented for <{}>".format( - self.__clas__.__name__ - ) - ) - - def width(self): - raise NotImplementedError( - "Attribute `width` is not implemented for <{}>".format( - self.__clas__.__name__ - ) - ) - - def collect_data(self): - return None - - def find_item(self, obj_type=None, name=None): - obj_type_fits = False - name_fits = False - if obj_type is None or self.obj_type == obj_type: - obj_type_fits = True - - if name is None or self.name != name: - name_fits = True - - output = [] - if obj_type_fits and name_fits: - output.append(self) - - if not self.items: - return output - - for item in self.items.values(): - output.extend( - item.find_item(obj_type=obj_type, name=name) - ) - return output - - @property - def full_style(self): - if self.parent is not None: - style = dict(val for val in self.parent.full_style.items()) - else: - style = self.main_style - - for key, value in self._style.items(): - if key in self.all_style_keys: - # TODO which variant is right? - style[self.obj_type][key] = value - # style["*"][key] = value - else: - if key not in style: - style[key] = {} - - if isinstance(style[key], dict): - style[key].update(value) - else: - style[key] = value - - return style - - def get_style_for_obj_type(self, obj_type, style=None): - if not style: - style = copy.deepcopy(self.full_style) - - base = style.get("*") or {} - obj_specific = style.get(obj_type) or {} - name_specific = {} - if self.name: - name = str(self.name) - if not name.startswith("#"): - name = "#" + name - name_specific = style.get(name) or {} - - if obj_type == "table-item": - col_regex = r"table-item-col\[([\d\-, ]+)*\]" - row_regex = r"table-item-row\[([\d\-, ]+)*\]" - field_regex = ( - r"table-item-field\[(([ ]+)?\d+([ ]+)?:([ ]+)?\d+([ ]+)?)*\]" - ) - # STRICT field regex (not allowed spaces) - # fild_regex = r"table-item-field\[(\d+:\d+)*\]" - - def get_indexes_from_regex_match(result, field=False): - group = result.group(1) - indexes = [] - if field: - return [ - int(part.strip()) for part in group.strip().split(":") - ] - - parts = group.strip().split(",") - for part in parts: - part = part.strip() - if "-" not in part: - indexes.append(int(part)) - continue - - sub_parts = [ - int(sub.strip()) for sub in part.split("-") - ] - if len(sub_parts) != 2: - # TODO logging - print("invalid range '{}'".format(part)) - continue - - for idx in range(sub_parts[0], sub_parts[1]+1): - indexes.append(idx) - return indexes - - for key, value in style.items(): - if not key.startswith(obj_type): - continue - - result = re.search(col_regex, key) - if result: - indexes = get_indexes_from_regex_match(result) - if self.col_idx in indexes: - obj_specific.update(value) - continue - - result = re.search(row_regex, key) - if result: - indexes = get_indexes_from_regex_match(result) - if self.row_idx in indexes: - obj_specific.update(value) - continue - - result = re.search(field_regex, key) - if result: - col_idx, row_idx = get_indexes_from_regex_match( - result, True - ) - if self.col_idx == col_idx and self.row_idx == row_idx: - obj_specific.update(value) - - output = {} - output.update(base) - output.update(obj_specific) - output.update(name_specific) - - return output - - @property - def style(self): - return self.get_style_for_obj_type(self.obj_type) - - @property - def item_pos_x(self): - if self.parent.obj_type == "main_frame": - return int(self._pos_x) - return 0 - - @property - def item_pos_y(self): - if self.parent.obj_type == "main_frame": - return int(self._pos_y) - return 0 - - @property - def content_pos_x(self): - pos_x = self.item_pos_x - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - - pos_x += margin_left - - return pos_x - - @property - def content_pos_y(self): - pos_y = self.item_pos_y - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - return pos_y + margin_top - - @property - def value_pos_x(self): - pos_x = int(self.content_pos_x) - padding = self.style["padding"] - padding_left = self.style.get("padding-left") - if padding_left is None: - padding_left = padding - - pos_x += padding_left - - return pos_x - - @property - def value_pos_y(self): - pos_y = int(self.content_pos_y) - padding = self.style["padding"] - padding_top = self.style.get("padding-top") - if padding_top is None: - padding_top = padding - - pos_y += padding_top - - return pos_y - - @property - def value_pos_start(self): - return (self.value_pos_x, self.value_pos_y) - - @property - def value_pos_end(self): - pos_x, pos_y = self.value_pos_start - pos_x += self.width() - pos_y += self.height() - return (pos_x, pos_y) - - @property - def content_pos_start(self): - return (self.content_pos_x, self.content_pos_y) - - @property - def content_pos_end(self): - pos_x, pos_y = self.content_pos_start - pos_x += self.content_width() - pos_y += self.content_height() - return (pos_x, pos_y) - - def value_width(self): - raise NotImplementedError( - "Attribute is not implemented <{}>".format( - self.__class__.__name__ - ) - ) - - def value_height(self): - raise NotImplementedError( - "Attribute is not implemented for <{}>".format( - self.__class__.__name__ - ) - ) - - def content_width(self): - width = self.value_width() - padding = self.style["padding"] - padding_left = self.style.get("padding-left") - if padding_left is None: - padding_left = padding - - padding_right = self.style.get("padding-right") - if padding_right is None: - padding_right = padding - - return width + padding_left + padding_right - - def content_height(self): - height = self.value_height() - padding = self.style["padding"] - padding_top = self.style.get("padding-top") - if padding_top is None: - padding_top = padding - - padding_bottom = self.style.get("padding-bottom") - if padding_bottom is None: - padding_bottom = padding - - return height + padding_top + padding_bottom - - def width(self): - width = self.content_width() - - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - margin_right = self.style.get("margin-right") or margin - - return width + margin_left + margin_right - - def height(self): - height = self.content_height() - - margin = self.style["margin"] - margin_top = self.style.get("margin-top") or margin - margin_bottom = self.style.get("margin-bottom") or margin - - return height + margin_bottom + margin_top - - def add_item(self, item): - self.items[item.id] = item - item.fill_data_format() - - - def reset(self): - for item in self.items.values(): - item.reset() - - -class MainFrame(BaseObj): - - obj_type = "main_frame" - available_parents = [None] - - def __init__( - self, width, height, destination_path, fill_data={}, *args, **kwargs - ): - kwargs["parent"] = None - super(MainFrame, self).__init__(*args, **kwargs) - self._width = width - self._height = height - self.dst_path = destination_path - self._fill_data = fill_data - self.fill_data_format() - - def fill_data_format(self): - if re.match(self.fill_data_regex, self.dst_path): - self.dst_path = self.dst_path.format(**self.fill_data) - - @property - def fill_data(self): - return self._fill_data - - def value_width(self): - width = 0 - for item in self.items.values(): - width += item.width() - return width - - def value_height(self): - height = 0 - for item in self.items.values(): - height += item.height() - return height - - def width(self): - return self._width - - def height(self): - return self._height - - def draw(self, path=None): - dir_path = os.path.dirname(self.dst_path) - if not os.path.exists(dir_path): - os.makedirs(dir_path) - - bg_color = self.style["bg-color"] - image = Image.new("RGB", (self.width(), self.height()), color=bg_color) - drawer = ImageDraw.Draw(image) - for item in self.items.values(): - item.draw(image, drawer) - - image.save(self.dst_path) - self.reset() - - def collect_data(self): - output = {} - output["width"] = self.width() - output["height"] = self.height() - output["slate_path"] = self.dst_path - - placeholders = self.find_item(obj_type="placeholder") - placeholders_data = [] - for placeholder in placeholders: - placeholders_data.append(placeholder.collect_data()) - - output["placeholders"] = placeholders_data - - return output - - -class Layer(BaseObj): - obj_type = "layer" - available_parents = ["main_frame", "layer"] - - # Direction can be 0=vertical/ 1=horizontal - def __init__(self, direction=0, *args, **kwargs): - super(Layer, self).__init__(*args, **kwargs) - self._direction = direction - - @property - def item_pos_x(self): - if self.parent.obj_type == self.obj_type: - pos_x = self.parent.child_pos_x(self.id) - elif self.parent.obj_type == "main_frame": - pos_x = self._pos_x - else: - pos_x = self.parent.value_pos_x - return int(pos_x) - - @property - def item_pos_y(self): - if self.parent.obj_type == self.obj_type: - pos_y = self.parent.child_pos_y(self.id) - elif self.parent.obj_type == "main_frame": - pos_y = self._pos_y - else: - pos_y = self.parent.value_pos_y - - return int(pos_y) - - @property - def direction(self): - if self._direction not in (0, 1): - print( - "Direction must be 0 or 1 (0 is horizontal / 1 is vertical)!" - ) - return 0 - return self._direction - - def child_pos_x(self, item_id): - pos_x = self.value_pos_x - alignment_hor = self.style["alignment-horizontal"].lower() - - item = None - for id, _item in self.items.items(): - if item_id == id: - item = _item - break - - if self.direction == 1: - for id, _item in self.items.items(): - if item_id == id: - break - - pos_x += _item.width() - if _item.obj_type not in ["image", "placeholder"]: - pos_x += 1 - - else: - if alignment_hor in ["center", "centre"]: - pos_x += (self.content_width() - item.content_width()) / 2 - - elif alignment_hor == "right": - pos_x += self.content_width() - item.content_width() - - else: - margin = self.style["margin"] - margin_left = self.style.get("margin-left") or margin - pos_x += margin_left - - return int(pos_x) - - def child_pos_y(self, item_id): - pos_y = self.value_pos_y - alignment_ver = self.style["alignment-horizontal"].lower() - - item = None - for id, _item in self.items.items(): - if item_id == id: - item = _item - break - - if self.direction != 1: - for id, item in self.items.items(): - if item_id == id: - break - pos_y += item.height() - if item.obj_type not in ["image", "placeholder"]: - pos_y += 1 - - else: - if alignment_ver in ["center", "centre"]: - pos_y += (self.content_height() - item.content_height()) / 2 - - elif alignment_ver == "bottom": - pos_y += self.content_height() - item.content_height() - - return int(pos_y) - - def value_height(self): - height = 0 - for item in self.items.values(): - if self.direction == 1: - if height > item.height(): - continue - # times 1 because won't get object pointer but number - height = item.height() - else: - height += item.height() - - # TODO this is not right - min_height = self.style.get("min-height") - if min_height and min_height > height: - return min_height - return height - - def value_width(self): - width = 0 - for item in self.items.values(): - if self.direction == 0: - if width > item.width(): - continue - # times 1 because won't get object pointer but number - width = item.width() - else: - if self.name == "LeftSide": - print(item, item.width()) - width += item.width() - - min_width = self.style.get("min-width") - if min_width and min_width > width: - return min_width - return width - - def draw(self, image, drawer): - for item in self.items.values(): - item.draw(image, drawer) - - -class BaseItem(BaseObj): - available_parents = ["main_frame", "layer"] - - @property - def item_pos_x(self): - if self.parent.obj_type == "main_frame": - return self._pos_x - return self.parent.child_pos_x(self.id) - - @property - def item_pos_y(self): - if self.parent.obj_type == "main_frame": - return self._pos_y - return self.parent.child_pos_y(self.id) - - def add_item(self, *args, **kwargs): - raise Exception("Can't add item to an item, use layers instead.") - - def draw(self, image, drawer): - raise NotImplementedError( - "Method `draw` is not implemented for <{}>".format( - self.__clas__.__name__ - ) - ) - - -class ItemImage(BaseItem): - obj_type = "image" - - def __init__(self, image_path, *args, **kwargs): - super(ItemImage, self).__init__(*args, **kwargs) - self.image_path = image_path - - def fill_data_format(self): - if re.match(self.fill_data_regex, self.image_path): - self.image_path = self.image_path.format(**self.fill_data) - - def draw(self, image, drawer): - source_image = Image.open(os.path.normpath(self.image_path)) - paste_image = source_image.resize( - (self.value_width(), self.value_height()), - Image.ANTIALIAS - ) - image.paste( - paste_image, - (self.value_pos_x, self.value_pos_y) - ) - - def value_width(self): - return int(self.style["width"]) - - def value_height(self): - return int(self.style["height"]) - - -class ItemRectangle(BaseItem): - obj_type = "rectangle" - - def draw(self, image, drawer): - bg_color = self.style["bg-color"] - fill = self.style.get("fill", False) - kwargs = {} - if fill: - kwargs["fill"] = bg_color - else: - kwargs["outline"] = bg_color - - start_pos_x = self.value_pos_x - start_pos_y = self.value_pos_y - end_pos_x = start_pos_x + self.value_width() - end_pos_y = start_pos_y + self.value_height() - drawer.rectangle( - ( - (start_pos_x, start_pos_y), - (end_pos_x, end_pos_y) - ), - **kwargs - ) - - def value_width(self): - return int(self.style["width"]) - - def value_height(self): - return int(self.style["height"]) - - -class ItemPlaceHolder(BaseItem): - obj_type = "placeholder" - - def __init__(self, image_path, *args, **kwargs): - self.image_path = image_path - super(ItemPlaceHolder, self).__init__(*args, **kwargs) - - def fill_data_format(self): - if re.match(self.fill_data_regex, self.image_path): - self.image_path = self.image_path.format(**self.fill_data) - - def draw(self, image, drawer): - bg_color = self.style["bg-color"] - - kwargs = {} - if bg_color != "tranparent": - kwargs["fill"] = bg_color - - start_pos_x = self.value_pos_x - start_pos_y = self.value_pos_y - end_pos_x = start_pos_x + self.value_width() - end_pos_y = start_pos_y + self.value_height() - - drawer.rectangle( - ( - (start_pos_x, start_pos_y), - (end_pos_x, end_pos_y) - ), - **kwargs - ) - - def value_width(self): - return int(self.style["width"]) - - def value_height(self): - return int(self.style["height"]) - - def collect_data(self): - return { - "pos_x": self.value_pos_x, - "pos_y": self.value_pos_y, - "width": self.value_width(), - "height": self.value_height(), - "path": self.image_path - } - - -class ItemText(BaseItem): - obj_type = "text" - - def __init__(self, value, *args, **kwargs): - super(ItemText, self).__init__(*args, **kwargs) - self.value = value - - def draw(self, image, drawer): - bg_color = self.style["bg-color"] - if bg_color and bg_color.lower() != "transparent": - # TODO border outline styles - drawer.rectangle( - (self.content_pos_start, self.content_pos_end), - fill=bg_color, - outline=None - ) - - font_color = self.style["font-color"] - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - drawer.text( - self.value_pos_start, - self.value, - font=font, - fill=font_color - ) - - def value_width(self): - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - width = font.getsize(self.value)[0] - return int(width) - - def value_height(self): - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - height = font.getsize(self.value)[1] - return int(height) - - -class ItemTable(BaseItem): - - obj_type = "table" - - def __init__(self, values, use_alternate_color=False, *args, **kwargs): - - self.values_by_cords = None - self.prepare_values(values) - - super(ItemTable, self).__init__(*args, **kwargs) - self.size_values = None - self.calculate_sizes() - - self.use_alternate_color = use_alternate_color - - def add_item(self, item): - if item.obj_type == "table-item": - return - super(ItemTable, self).add_item(item) - - def fill_data_format(self): - for item in self.values: - item.fill_data_format() - - def prepare_values(self, _values): - values = [] - values_by_cords = [] - row_count = 0 - col_count = 0 - for row in _values: - row_count += 1 - if len(row) > col_count: - col_count = len(row) - - for row_idx in range(row_count): - values_by_cords.append([]) - for col_idx in range(col_count): - values_by_cords[row_idx].append([]) - if col_idx <= len(_values[row_idx]) - 1: - col = _values[row_idx][col_idx] - else: - col = "" - - col_item = TableField(row_idx, col_idx, col, parent=self) - values_by_cords[row_idx][col_idx] = col_item - values.append(col_item) - - self.values = values - self.values_by_cords = values_by_cords - - def calculate_sizes(self): - row_heights = [] - col_widths = [] - for row_idx, row in enumerate(self.values_by_cords): - row_heights.append(0) - for col_idx, col_item in enumerate(row): - if len(col_widths) < col_idx + 1: - col_widths.append(0) - - _width = col_widths[col_idx] - item_width = col_item.width() - if _width < item_width: - col_widths[col_idx] = item_width - - _height = row_heights[row_idx] - item_height = col_item.height() - if _height < item_height: - row_heights[row_idx] = item_height - - self.size_values = (row_heights, col_widths) - - def draw(self, image, drawer): - bg_color = self.style["bg-color"] - if bg_color and bg_color.lower() != "transparent": - # TODO border outline styles - drawer.rectangle( - (self.content_pos_start, self.content_pos_end), - fill=bg_color, - outline=None - ) - - for value in self.values: - value.draw(image, drawer) - - def value_width(self): - row_heights, col_widths = self.size_values - width = 0 - for _width in col_widths: - width += _width - - if width != 0: - width -= 1 - return width - - def value_height(self): - row_heights, col_widths = self.size_values - height = 0 - for _height in row_heights: - height += _height - - if height != 0: - height -= 1 - return height - - def content_pos_info_by_cord(self, row_idx, col_idx): - row_heights, col_widths = self.size_values - pos_x = int(self.value_pos_x) - pos_y = int(self.value_pos_y) - width = 0 - height = 0 - for idx, value in enumerate(col_widths): - if col_idx == idx: - width = value - break - pos_x += value - - for idx, value in enumerate(row_heights): - if row_idx == idx: - height = value - break - pos_y += value - - return (pos_x, pos_y, width, height) - - -class TableField(BaseItem): - - obj_type = "table-item" - available_parents = ["table"] - ellide_text = "..." - - def __init__(self, row_idx, col_idx, value, *args, **kwargs): - super(TableField, self).__init__(*args, **kwargs) - self.row_idx = row_idx - self.col_idx = col_idx - self.value = value - - def recalculate_by_width(self, value, max_width): - padding = self.style["padding"] - padding_left = self.style.get("padding-left") - if padding_left is None: - padding_left = padding - - padding_right = self.style.get("padding-right") - if padding_right is None: - padding_right = padding - - max_width -= (padding_left + padding_right) - - if not value: - return "" - - word_wrap = self.style.get("word-wrap") - ellide = self.style.get("ellide") - max_lines = self.style.get("max-lines") - - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - val_width = font.getsize(value)[0] - if val_width <= max_width: - return value - - if not ellide and not word_wrap: - # TODO logging - print(( - "Can't draw text because is too long with" - " `word-wrap` and `ellide` turned off" - )) - return "" - - elif ellide and not word_wrap: - max_lines = 1 - - words = [word for word in value.split()] - words_len = len(words) - lines = [] - last_index = None - while True: - start_index = 0 - if last_index is not None: - start_index = int(last_index) + 1 - - line = "" - for idx in range(start_index, words_len): - _word = words[idx] - connector = " " - if line == "": - connector = "" - - _line = connector.join([line, _word]) - _line_width = font.getsize(_line)[0] - if _line_width > max_width: - break - line = _line - last_index = idx - - if line: - lines.append(line) - - if last_index == words_len - 1: - break - - elif last_index is None: - if ellide: - line = "" - for idx, char in enumerate(words[idx]): - _line = line + char + self.ellide_text - _line_width = font.getsize(_line)[0] - if _line_width > max_width: - if idx == 0: - line = _line - break - line = line + char - - lines.append(line) - # TODO logging - print("Font size is too big.") - break - - output = "" - if not lines: - return output - - over_max_lines = (max_lines and len(lines) > max_lines) - if not over_max_lines: - return "\n".join([line for line in lines]) - - lines = [lines[idx] for idx in range(max_lines)] - if not ellide: - return "\n".join(lines) - - last_line = lines[-1] - last_line_width = font.getsize(last_line + self.ellide_text)[0] - if last_line_width <= max_width: - lines[-1] += self.ellide_text - return "\n".join([line for line in lines]) - - last_line_words = last_line.split() - if len(last_line_words) == 1: - if max_lines > 1: - # TODO try previous line? - lines[-1] = self.ellide_text - return "\n".join([line for line in lines]) - - line = "" - for idx, word in enumerate(last_line_words): - _line = line + word + self.ellide_text - _line_width = font.getsize(_line)[0] - if _line_width > max_width: - if idx == 0: - line = _line - break - line = _line - lines[-1] = line - - return "\n".join([line for line in lines]) - - line = "" - for idx, _word in enumerate(last_line_words): - connector = " " - if line == "": - connector = "" - - _line = connector.join([line, _word + self.ellide_text]) - _line_width = font.getsize(_line)[0] - - if _line_width <= max_width: - line = connector.join([line, _word]) - continue - - if idx != 0: - line += self.ellide_text - break - - if max_lines != 1: - # TODO try previous line? - line = self.ellide_text - break - - for idx, char in enumerate(_word): - _line = line + char + self.ellide_text - _line_width = font.getsize(_line)[0] - if _line_width > max_width: - if idx == 0: - line = _line - break - line = line + char - break - - lines[-1] = line - - return "\n".join([line for line in lines]) - - def fill_data_format(self): - value = self.value - if re.match(self.fill_data_regex, value): - value = value.format(**self.fill_data) - - self.orig_value = value - - max_width = self.style.get("max-width") - max_width = self.style.get("width") or max_width - if max_width: - value = self.recalculate_by_width(value, max_width) - - self.value = value - - def content_width(self): - width = self.style.get("width") - if width: - return int(width) - return super(TableField, self).content_width() - - def content_height(self): - return super(TableField, self).content_height() - - def value_width(self): - if not self.value: - return 0 - - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - width = font.getsize_multiline(self.value)[0] + 1 - - min_width = self.style.get("min-height") - if min_width and min_width > width: - width = min_width - - return int(width) - - def value_height(self): - if not self.value: - return 0 - - height = self.style.get("height") - if height: - return int(height) - - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - height = font.getsize_multiline(self.value)[1] + 1 - - min_height = self.style.get("min-height") - if min_height and min_height > height: - height = min_height - - return int(height) - - @property - def item_pos_x(self): - pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) - ) - return pos_x - - @property - def item_pos_y(self): - pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) - ) - return pos_y - - @property - def value_pos_x(self): - pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) - ) - alignment_hor = self.style["alignment-horizontal"].lower() - if alignment_hor in ["center", "centre"]: - pos_x += (width - self.value_width()) / 2 - - elif alignment_hor == "right": - pos_x += width - self.value_width() - - else: - padding = self.style["padding"] - padding_left = self.style.get("padding-left") - if padding_left is None: - padding_left = padding - - pos_x += padding_left - - return int(pos_x) - - @property - def value_pos_y(self): - pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) - ) - - alignment_ver = self.style["alignment-vertical"].lower() - if alignment_ver in ["center", "centre"]: - pos_y += (height - self.value_height()) / 2 - - elif alignment_ver == "bottom": - pos_y += height - self.value_height() - - else: - padding = self.style["padding"] - padding_top = self.style.get("padding-top") - if padding_top is None: - padding_top = padding - - pos_y += padding_top - - return int(pos_y) - - def draw(self, image, drawer): - pos_x, pos_y, width, height = ( - self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) - ) - pos_start = (pos_x, pos_y) - pos_end = (pos_x + width, pos_y + height) - bg_color = self.style["bg-color"] - if self.parent.use_alternate_color and (self.row_idx % 2) == 1: - bg_color = self.style["bg-alter-color"] - - if bg_color and bg_color.lower() != "transparent": - # TODO border outline styles - drawer.rectangle( - (pos_start, pos_end), - fill=bg_color, - outline=None - ) - - font_color = self.style["font-color"] - font_family = self.style["font-family"] - font_size = self.style["font-size"] - font_bold = self.style.get("font-bold", False) - font_italic = self.style.get("font-italic", False) - - font = FontFactory.get_font( - font_family, font_size, font_italic, font_bold - ) - - alignment_hor = self.style["alignment-horizontal"].lower() - if alignment_hor == "centre": - alignment_hor = "center" - - drawer.multiline_text( - self.value_pos_start, - self.value, - font=font, - fill=font_color, - align=alignment_hor - ) - - -class FontFactory: - fonts = None - default = None - - @classmethod - def get_font(cls, family, font_size=None, italic=False, bold=False): - if cls.fonts is None: - cls.load_fonts() - - styles = [] - if bold: - styles.append("Bold") - - if italic: - styles.append("Italic") - - if not styles: - styles.append("Regular") - - style = " ".join(styles) - family = family.lower() - family_styles = cls.fonts.get(family) - if not family_styles: - return cls.default - - font = family_styles.get(style) - if font: - if font_size: - font = font.font_variant(size=font_size) - return font - - # Return first found - for font in family_styles: - if font_size: - font = font.font_variant(size=font_size) - return font - - return cls.default - - @classmethod - def load_fonts(cls): - - cls.default = ImageFont.load_default() - - available_font_ext = [".ttf", ".ttc"] - dirs = [] - if sys.platform == "win32": - # check the windows font repository - # NOTE: must use uppercase WINDIR, to work around bugs in - # 1.5.2's os.environ.get() - windir = os.environ.get("WINDIR") - if windir: - dirs.append(os.path.join(windir, "fonts")) - - elif sys.platform in ("linux", "linux2"): - lindirs = os.environ.get("XDG_DATA_DIRS", "") - if not lindirs: - # According to the freedesktop spec, XDG_DATA_DIRS should - # default to /usr/share - lindirs = "/usr/share" - dirs += [ - os.path.join(lindir, "fonts") for lindir in lindirs.split(":") - ] - - elif sys.platform == "darwin": - dirs += [ - "/Library/Fonts", - "/System/Library/Fonts", - os.path.expanduser("~/Library/Fonts") - ] - - available_fonts = collections.defaultdict(dict) - for directory in dirs: - for walkroot, walkdir, walkfilenames in os.walk(directory): - for walkfilename in walkfilenames: - ext = os.path.splitext(walkfilename)[1] - if ext.lower() not in available_font_ext: - continue - - fontpath = os.path.join(walkroot, walkfilename) - font_obj = ImageFont.truetype(fontpath) - family = font_obj.font.family.lower() - style = font_obj.font.style - available_fonts[family][style] = font_obj - - cls.fonts = available_fonts - -def main(): - cur_folder = os.path.dirname(os.path.abspath(__file__)) - # input_json = os.path.join(cur_folder, "netflix_v01.json") - # input_json = os.path.join(cur_folder, "netflix_v02.json") - input_json = os.path.join(cur_folder, "netflix_v03.json") - with open(input_json) as json_file: - slate_data = json.load(json_file) - - fill_data = { - "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v03.png", - "project": { - "name": "Project name" - }, - "intent": "WIP", - "version_name": "mei_101_001_0020_slate_NFX_v001", - "date": "2019-08-09", - "shot_type": "2d comp", - "submission_note": "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.", - "thumbnail_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", - "color_bar_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", - "vendor": "DAZZLE", - "shot_name": "SLATE_SIMPLE", - "frame_start": 1001, - "frame_end": 1004, - "duration": 3 - } - width = slate_data["width"] - height = slate_data["height"] - dst_path = slate_data["destination_path"] - style = slate_data.get("style") or {} - - main = MainFrame(width, height, dst_path, fill_data, style=style) - - load_queue = Queue() - for item in slate_data["items"]: - load_queue.put((item, main)) - - while not load_queue.empty(): - item_data, parent = load_queue.get() - - item_type = item_data["type"].lower() - item_style = item_data.get("style", {}) - item_name = item_data.get("name") - - pos_x = None - pos_y = None - if parent.obj_type == "main_frame": - pos_x = item_data.get("pos_x", {}) - pos_y = item_data.get("pos_y", {}) - - kwargs = { - "parent": parent, - "style": item_style, - "name": item_name, - "pos_x": pos_x, - "pos_y": pos_y - } - - if item_type == "layer": - direction = item_data.get("direction", 0) - item_obj = Layer(direction, **kwargs) - for item in item_data.get("items", []): - load_queue.put((item, item_obj)) - - elif item_type == "table": - use_alternate_color = item_data.get("use_alternate_color", False) - values = item_data.get("values") or [] - ItemTable(values, use_alternate_color, **kwargs) - - elif item_type == "image": - path = item_data["path"] - ItemImage(path, **kwargs) - - elif item_type == "rectangle": - ItemRectangle(**kwargs) - - elif item_type == "placeholder": - path = item_data["path"] - ItemPlaceHolder(path, **kwargs) - - else: - # TODO logging - print( - "Slate item not implemented <{}> - skipping".format(item_type) - ) - - main.draw() - print(main.collect_data()) - print("*** Finished") - - -if __name__ == "__main__": - main() diff --git a/pype/scripts/slate/default_style.json b/pype/scripts/slate/default_style.json deleted file mode 100644 index c0f1006a4a..0000000000 --- a/pype/scripts/slate/default_style.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "*": { - "font-family": "arial", - "font-size": 26, - "font-color": "#ffffff", - "font-bold": false, - "font-italic": false, - "bg-color": "#777777", - "bg-alter-color": "#666666", - "__alignment-vertical__values": ["left", "center", "right"], - "alignment-vertical": "left", - "__alignment-horizontal__values": ["top", "center", "bottom"], - "alignment-horizontal": "top", - "__padding__variants": [ - "padding-top", "padding-right", "padding-bottom", "padding-left" - ], - "padding": 0, - "__margin__variants": [ - "margin-top", "margin-right", "margin-bottom", "margin-left" - ], - "margin": 0, - }, - "layer": { - - }, - "image": { - - }, - "text": { - - }, - "table": { - - }, - "table-item": { - - }, - "table-item-col-0": { - - }, - "#MyName": { - - } -} diff --git a/pype/scripts/slate/lib.py b/pype/scripts/slate/lib.py new file mode 100644 index 0000000000..ca3c0f2e41 --- /dev/null +++ b/pype/scripts/slate/lib.py @@ -0,0 +1,114 @@ +import os +import json +from queue import Queue + +# --- Lines for debug purpose --------------------------------- +import sys +sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") +# ------------------------------------------------------------- + +from slate_base.main_frame import MainFrame +from slate_base.layer import Layer +from slate_base.items import ( + ItemTable, ItemImage, ItemRectangle, ItemPlaceHolder +) + + +def main(fill_data): + cur_folder = os.path.dirname(os.path.abspath(__file__)) + input_json = os.path.join(cur_folder, "netflix_v03.json") + with open(input_json) as json_file: + slate_data = json.load(json_file) + + + width = slate_data["width"] + height = slate_data["height"] + dst_path = slate_data["destination_path"] + style = slate_data.get("style") or {} + + main = MainFrame(width, height, dst_path, fill_data, style=style) + + load_queue = Queue() + for item in slate_data["items"]: + load_queue.put((item, main)) + + while not load_queue.empty(): + item_data, parent = load_queue.get() + + item_type = item_data["type"].lower() + item_style = item_data.get("style", {}) + item_name = item_data.get("name") + + pos_x = item_data.get("pos_x") + pos_y = item_data.get("pos_y") + if parent.obj_type != "main_frame": + if pos_x or pos_y: + # TODO logging + self.log.warning(( + "You have specified `pos_x` and `pos_y` but won't be used." + " Possible only if parent of an item is `main_frame`." + )) + pos_x = None + pos_y = None + + kwargs = { + "parent": parent, + "style": item_style, + "name": item_name, + "pos_x": pos_x, + "pos_y": pos_y + } + + if item_type == "layer": + direction = item_data.get("direction", 0) + item_obj = Layer(direction, **kwargs) + for item in item_data.get("items", []): + load_queue.put((item, item_obj)) + + elif item_type == "table": + use_alternate_color = item_data.get("use_alternate_color", False) + values = item_data.get("values") or [] + ItemTable(values, use_alternate_color, **kwargs) + + elif item_type == "image": + path = item_data["path"] + ItemImage(path, **kwargs) + + elif item_type == "rectangle": + ItemRectangle(**kwargs) + + elif item_type == "placeholder": + path = item_data["path"] + ItemPlaceHolder(path, **kwargs) + + else: + # TODO logging + self.log.warning( + "Slate item not implemented <{}> - skipping".format(item_type) + ) + + main.draw() + print("Slate creation finished") + + +if __name__ == "__main__": + fill_data = { + "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v03.png", + "project": { + "name": "Project name" + }, + "intent": "WIP", + "version_name": "mei_101_001_0020_slate_NFX_v001", + "date": "2019-08-09", + "shot_type": "2d comp", + "submission_note": "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.", + "thumbnail_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", + "color_bar_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", + "vendor": "DAZZLE", + "shot_name": "SLATE_SIMPLE", + "frame_start": 1001, + "frame_end": 1004, + "duration": 3 + } + main(fill_data) + # raise NotImplementedError("Slates don't have Implemented args running") diff --git a/pype/scripts/slate/slate.py b/pype/scripts/slate/slate.py deleted file mode 100644 index 4e66f68d21..0000000000 --- a/pype/scripts/slate/slate.py +++ /dev/null @@ -1,355 +0,0 @@ -import textwrap -from PIL import Image, ImageFont, ImageDraw, ImageEnhance, ImageColor - - -class TableDraw: - def __init__( - self, image_width, image_height, - rel_pos_x, rel_pos_y, rel_width, - col_fonts=None, col_font_colors=None, - default_font=None, default_font_color=None, - col_alignments=None, rel_col_widths=None, bg_color=None, - alter_bg_color=None, pad=20, - pad_top=None, pad_bottom=None, pad_left=None, pad_right=None - ): - self.image_width = image_width - - pos_x = image_width * rel_pos_x - pos_y = image_height * rel_pos_y - width = image_width * rel_width - - self.pos_x_start = pos_x - self.pos_y_start = pos_y - self.pos_x_end = pos_x + width - self.width = width - - self.rel_col_widths = list(rel_col_widths) - self._col_widths = None - self._col_alignments = col_alignments - - if bg_color and isinstance(bg_color, str): - bg_color = ImageColor.getrgb(bg_color) - - if alter_bg_color and isinstance(alter_bg_color, str): - alter_bg_color = ImageColor.getrgb(alter_bg_color) - - self._bg_color = bg_color - self._alter_bg_color = alter_bg_color - - self.alter_use = False - - if col_fonts: - _col_fonts = [] - for col_font in col_fonts: - font_name, font_size = col_font - font = ImageFont.truetype(font_name, font_size) - _col_fonts.append(font) - col_fonts = _col_fonts - - self._col_fonts = col_fonts - - self._col_font_colors = col_font_colors - - if not default_font: - default_font = ImageFont.truetype("times", 26) - self.default_font = default_font - - if not default_font_color: - default_font_color = "#ffffff" - self.default_font_color = default_font_color - - self.texts = [] - - if pad is None: - pad = 5 - - _pad_top = pad - if pad_top is not None: - _pad_top = pad_top - - _pad_bottom = pad - if pad_bottom is not None: - _pad_bottom = pad_bottom - - _pad_left = pad - if pad_left is not None: - _pad_left = pad_left - - _pad_right = pad - if pad_right is not None: - _pad_right = pad_right - - self.pad_top = _pad_top - self.pad_bottom = _pad_bottom - self.pad_left = _pad_left - self.pad_right = _pad_right - - @property - def col_widths(self): - if self._col_widths is None: - sum_width = 0 - for w in self.rel_col_widths: - sum_width += w - - one_piece = self.width / sum_width - self._col_widths = [] - for w in self.rel_col_widths: - self._col_widths.append(one_piece * w) - - return self._col_widths - - @property - def col_fonts(self): - if self._col_fonts is None: - self._col_fonts = [] - for _ in range(len(self.col_widths)): - self._col_fonts.append(self.default_font) - - elif len(self._col_fonts) < len(self.col_widths): - if isinstance(self._col_fonts, tuple): - self._col_fonts = list(self._col_fonts) - - while len(self._col_fonts) < len(self.col_widths): - self._col_fonts.append(self.default_font) - - return self._col_fonts - - @property - def col_font_colors(self): - if self._col_font_colors is None: - self._col_font_colors = [] - for _ in range(len(self.col_widths)): - self._col_font_colors.append(self.default_font_color) - - elif len(self._col_font_colors) < len(self.col_widths): - if isinstance(self._col_font_colors, tuple): - self._col_font_colors = list(self._col_font_colors) - - while len(self._col_font_colors) < len(self.col_widths): - self._col_font_colors.append(self.default_font_color) - - return self._col_font_colors - - @property - def col_alignments(self): - if self._col_alignments is None: - self._col_alignments = [] - for _ in range(len(self.col_widths)): - self._col_alignments.append("left") - - elif len(self._col_alignments) < len(self.col_widths): - if isinstance(self._col_alignments, tuple): - self._col_alignments = list(self._col_alignments) - - while len(self._col_alignments) < len(self.col_widths): - self._col_alignments.append("left") - - return self._col_alignments - - @property - def bg_color(self): - if self.alter_use is True: - value = self.alter_bg_color - self.alter_use = False - else: - value = self._bg_color - self.alter_use = True - return value - - @property - def alter_bg_color(self): - if self._alter_bg_color: - return self._alter_bg_color - return self.bg_color - - def add_texts(self, texts): - if isinstance(texts, str): - texts = [texts] - - for text in texts: - if isinstance(text, str): - text = [text] - - if len(text) > len(self.rel_col_widths): - for _ in (len(text) - len(self.rel_col_widths)): - self.rel_col_widths.append(1) - for _t in self.texts: - _t.append("") - - self.texts.append(text) - - def draw(self, drawer): - y_pos = self.pos_y_start - for texts in self.texts: - max_height = None - cols_data = [] - for _idx, col in enumerate(texts): - width = self.col_widths[_idx] - font = self.col_fonts[_idx] - lines, line_height = self.lines_height_by_width( - drawer, col, width - self.pad_left - self.pad_right, font - ) - row_height = line_height * len(lines) - if max_height is None or row_height > max_height: - max_height = row_height - - cols_data.append({ - "lines": lines, - "line_height": line_height - }) - - drawer.rectangle( - ( - (self.pos_x_start, y_pos), - ( - self.pos_x_end, - y_pos + max_height + self.pad_top + self.pad_bottom - ) - ), - fill=self.bg_color - ) - - pos_x_start = self.pos_x_start + self.pad_left - for col, col_data in enumerate(cols_data): - lines = col_data["lines"] - line_height = col_data["line_height"] - alignment = self.col_alignments[col] - x_offset = self.col_widths[col] - font = self.col_fonts[col] - font_color = self.col_font_colors[col] - for idx, line_data in enumerate(lines): - line = line_data["text"] - line_width = line_data["width"] - if alignment == "left": - x_start = pos_x_start + self.pad_left - elif alignment == "right": - x_start = ( - pos_x_start + x_offset - line_width - - self.pad_right - self.pad_left - ) - else: - # TODO else - x_start = pos_x_start + self.pad_left - - drawer.text( - ( - x_start, - y_pos + (idx * line_height) + self.pad_top - ), - line, - font=font, - fill=font_color - ) - pos_x_start += x_offset - - y_pos += max_height + self.pad_top + self.pad_bottom - - def lines_height_by_width(self, drawer, text, width, font): - lines = [] - lines.append([part for part in text.split() if part]) - - line = 0 - while True: - thistext = lines[line] - line = line + 1 - if not thistext: - break - newline = [] - - while True: - _textwidth = drawer.textsize(" ".join(thistext), font)[0] - if ( - _textwidth <= width - ): - break - elif _textwidth > width and len(thistext) == 1: - # TODO raise error? - break - - val = thistext.pop(-1) - - if not val: - break - newline.insert(0, val) - - if len(newline) > 0: - lines.append(newline) - else: - break - - _lines = [] - height = None - for line_items in lines: - line = " ".join(line_items) - (width, _height) = drawer.textsize(line, font) - if height is None or height < _height: - height = _height - - _lines.append({ - "width": width, - "text": line - }) - - return (_lines, height) - - -width = 1920 -height = 1080 -# width = 800 -# height = 600 -bg_color_hex = "#242424" -bg_color = ImageColor.getrgb(bg_color_hex) - -base = Image.new('RGB', (width, height), color=bg_color) - -texts = [ - ("Version name:", "mei_101_001_0020_slate_NFX_v001"), - ("Date:", "2019-08-09"), - ("Shot Types:", "2d comp"), - # ("Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.") -] -text_widths_rel = (2, 8) -col_alignments = ("right", "left") -fonts = (("arial", 20), ("times", 26)) -font_colors = ("#999999", "#ffffff") - -table_color_hex = "#212121" -table_alter_color_hex = "#272727" - -drawer = ImageDraw.Draw(base) -table_d = TableDraw( - width, height, - 0.1, 0.1, 0.5, - col_fonts=fonts, - col_font_colors=font_colors, - rel_col_widths=text_widths_rel, - col_alignments=col_alignments, - bg_color=table_color_hex, - alter_bg_color=table_alter_color_hex, - pad_top=20, pad_bottom=20, pad_left=5, pad_right=5 -) - -table_d.add_texts(texts) -table_d.draw(drawer) - -image_path = r"C:\Users\iLLiCiT\Desktop\Prace\Pillow\image.jpg" -image = Image.open(image_path) -img_width, img_height = image.size - -rel_image_width = 0.3 -rel_image_pos_x = 0.65 -rel_image_pos_y = 0.1 -image_pos_x = int(width * rel_image_pos_x) -image_pos_y = int(width * rel_image_pos_y) - -new_width = int(width * rel_image_width) -new_height = int(new_width * img_height / img_width) -image = image.resize((new_width, new_height), Image.ANTIALIAS) - -# mask = Image.new("L", image.size, 255) - -base.paste(image, (image_pos_x, image_pos_y)) -base.save(r"C:\Users\iLLiCiT\Desktop\Prace\Pillow\test{}x{}.jpg".format( - width, height -)) -base.show() diff --git a/pype/scripts/slate/slate_base/__init__.py b/pype/scripts/slate/slate_base/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/scripts/slate/slate_base/base.py b/pype/scripts/slate/slate_base/base.py new file mode 100644 index 0000000000..35a3b6af6d --- /dev/null +++ b/pype/scripts/slate/slate_base/base.py @@ -0,0 +1,373 @@ +import os +import re +import logging +import copy +import json +from uuid import uuid4 + + +def load_default_style(): + cur_folder = os.path.dirname(os.path.abspath(__file__)) + default_json_path = os.path.join(cur_folder, "default_style.json") + with open(default_json_path, "r") as _file: + data = _file.read() + return json.loads(data) + + +class BaseObj: + """Base Object for slates.""" + + obj_type = None + available_parents = [] + all_style_keys = [ + "font-family", "font-size", "font-color", "font-bold", "font-italic", + "bg-color", "bg-alter-color", + "alignment-horizontal", "alignment-vertical", + "padding", "padding-left", "padding-right", + "padding-top", "padding-bottom", + "margin", "margin-left", "margin-right", + "margin-top", "margin-bottom", "width", "height", + "fill", "word-wrap", "ellide", "max-lines" + ] + fill_data_regex = r"{[^}]+}" + + def __init__(self, parent, style={}, name=None, pos_x=None, pos_y=None): + if not self.obj_type: + raise NotImplementedError( + "Class don't have set object type <{}>".format( + self.__class__.__name__ + ) + ) + + parent_obj_type = None + if parent: + parent_obj_type = parent.obj_type + + if parent_obj_type not in self.available_parents: + expected_parents = ", ".join(self.available_parents) + raise Exception(( + "Invalid parent <{}> for <{}>. Expected <{}>" + ).format( + parent.__class__.__name__, self.obj_type, expected_parents + )) + + self.parent = parent + self._style = style + + self.id = uuid4() + self.name = name + self.items = {} + + self._pos_x = pos_x or 0 + self._pos_y = pos_y or 0 + + log_parts = [] + module = self.__class__.__module__ + if module and module != "__main__": + log_parts.append(module) + log_parts.append(self.__class__.__name__) + self.log = logging.getLogger(".".join(log_parts)) + + if parent: + parent.add_item(self) + + def fill_data_format(self): + return + + @property + def fill_data(self): + return self.parent.fill_data + + @property + def main_style(self): + return load_default_style() + + def height(self): + raise NotImplementedError( + "Attribute `height` is not implemented for <{}>".format( + self.__clas__.__name__ + ) + ) + + def width(self): + raise NotImplementedError( + "Attribute `width` is not implemented for <{}>".format( + self.__clas__.__name__ + ) + ) + + def collect_data(self): + return None + + def find_item(self, obj_type=None, name=None): + obj_type_fits = False + name_fits = False + if obj_type is None or self.obj_type == obj_type: + obj_type_fits = True + + if name is None or self.name != name: + name_fits = True + + output = [] + if obj_type_fits and name_fits: + output.append(self) + + if not self.items: + return output + + for item in self.items.values(): + output.extend( + item.find_item(obj_type=obj_type, name=name) + ) + return output + + @property + def full_style(self): + if self.parent is not None: + style = dict(val for val in self.parent.full_style.items()) + else: + style = self.main_style + + for key, value in self._style.items(): + if key in self.all_style_keys: + # TODO which variant is right? + style[self.obj_type][key] = value + # style["*"][key] = value + else: + if key not in style: + style[key] = {} + + if isinstance(style[key], dict): + style[key].update(value) + else: + style[key] = value + + return style + + def get_style_for_obj_type(self, obj_type, style=None): + if not style: + style = copy.deepcopy(self.full_style) + + base = style.get("*") or {} + obj_specific = style.get(obj_type) or {} + name_specific = {} + if self.name: + name = str(self.name) + if not name.startswith("#"): + name = "#" + name + name_specific = style.get(name) or {} + + if obj_type == "table-item": + col_regex = r"table-item-col\[([\d\-, ]+)*\]" + row_regex = r"table-item-row\[([\d\-, ]+)*\]" + field_regex = ( + r"table-item-field\[(([ ]+)?\d+([ ]+)?:([ ]+)?\d+([ ]+)?)*\]" + ) + # STRICT field regex (not allowed spaces) + # fild_regex = r"table-item-field\[(\d+:\d+)*\]" + + def get_indexes_from_regex_match(result, field=False): + group = result.group(1) + indexes = [] + if field: + return [ + int(part.strip()) for part in group.strip().split(":") + ] + + parts = group.strip().split(",") + for part in parts: + part = part.strip() + if "-" not in part: + indexes.append(int(part)) + continue + + sub_parts = [ + int(sub.strip()) for sub in part.split("-") + ] + if len(sub_parts) != 2: + # TODO logging + self.log.warning("Invalid range '{}'".format(part)) + continue + + for idx in range(sub_parts[0], sub_parts[1]+1): + indexes.append(idx) + return indexes + + for key, value in style.items(): + if not key.startswith(obj_type): + continue + + result = re.search(col_regex, key) + if result: + indexes = get_indexes_from_regex_match(result) + if self.col_idx in indexes: + obj_specific.update(value) + continue + + result = re.search(row_regex, key) + if result: + indexes = get_indexes_from_regex_match(result) + if self.row_idx in indexes: + obj_specific.update(value) + continue + + result = re.search(field_regex, key) + if result: + col_idx, row_idx = get_indexes_from_regex_match( + result, True + ) + if self.col_idx == col_idx and self.row_idx == row_idx: + obj_specific.update(value) + + output = {} + output.update(base) + output.update(obj_specific) + output.update(name_specific) + + return output + + @property + def style(self): + return self.get_style_for_obj_type(self.obj_type) + + @property + def item_pos_x(self): + if self.parent.obj_type == "main_frame": + return int(self._pos_x) + return 0 + + @property + def item_pos_y(self): + if self.parent.obj_type == "main_frame": + return int(self._pos_y) + return 0 + + @property + def content_pos_x(self): + pos_x = self.item_pos_x + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + + pos_x += margin_left + + return pos_x + + @property + def content_pos_y(self): + pos_y = self.item_pos_y + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + return pos_y + margin_top + + @property + def value_pos_x(self): + pos_x = int(self.content_pos_x) + padding = self.style["padding"] + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + + pos_x += padding_left + + return pos_x + + @property + def value_pos_y(self): + pos_y = int(self.content_pos_y) + padding = self.style["padding"] + padding_top = self.style.get("padding-top") + if padding_top is None: + padding_top = padding + + pos_y += padding_top + + return pos_y + + @property + def value_pos_start(self): + return (self.value_pos_x, self.value_pos_y) + + @property + def value_pos_end(self): + pos_x, pos_y = self.value_pos_start + pos_x += self.width() + pos_y += self.height() + return (pos_x, pos_y) + + @property + def content_pos_start(self): + return (self.content_pos_x, self.content_pos_y) + + @property + def content_pos_end(self): + pos_x, pos_y = self.content_pos_start + pos_x += self.content_width() + pos_y += self.content_height() + return (pos_x, pos_y) + + def value_width(self): + raise NotImplementedError( + "Attribute is not implemented <{}>".format( + self.__class__.__name__ + ) + ) + + def value_height(self): + raise NotImplementedError( + "Attribute is not implemented for <{}>".format( + self.__class__.__name__ + ) + ) + + def content_width(self): + width = self.value_width() + padding = self.style["padding"] + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + + padding_right = self.style.get("padding-right") + if padding_right is None: + padding_right = padding + + return width + padding_left + padding_right + + def content_height(self): + height = self.value_height() + padding = self.style["padding"] + padding_top = self.style.get("padding-top") + if padding_top is None: + padding_top = padding + + padding_bottom = self.style.get("padding-bottom") + if padding_bottom is None: + padding_bottom = padding + + return height + padding_top + padding_bottom + + def width(self): + width = self.content_width() + + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + margin_right = self.style.get("margin-right") or margin + + return width + margin_left + margin_right + + def height(self): + height = self.content_height() + + margin = self.style["margin"] + margin_top = self.style.get("margin-top") or margin + margin_bottom = self.style.get("margin-bottom") or margin + + return height + margin_bottom + margin_top + + def add_item(self, item): + self.items[item.id] = item + item.fill_data_format() + + + def reset(self): + for item in self.items.values(): + item.reset() diff --git a/pype/scripts/slate/slate_base/default_style.json b/pype/scripts/slate/slate_base/default_style.json new file mode 100644 index 0000000000..d0748846a5 --- /dev/null +++ b/pype/scripts/slate/slate_base/default_style.json @@ -0,0 +1,58 @@ +{ + "*": { + "font-family": "arial", + "font-size": 26, + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": true, + "ellide": true, + "max-lines": null + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "fill": true + }, + "image": { + "padding": 0, + "margin": 0, + "fill": true + }, + "placeholder": { + "padding": 0, + "margin": 0, + "fill": true + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 0, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 + } +} diff --git a/pype/scripts/slate/slate_base/font_factory.py b/pype/scripts/slate/slate_base/font_factory.py new file mode 100644 index 0000000000..77df9a40a7 --- /dev/null +++ b/pype/scripts/slate/slate_base/font_factory.py @@ -0,0 +1,93 @@ +import os +import sys +import collections + +from PIL import ImageFont + + +class FontFactory: + fonts = None + default = None + + @classmethod + def get_font(cls, family, font_size=None, italic=False, bold=False): + if cls.fonts is None: + cls.load_fonts() + + styles = [] + if bold: + styles.append("Bold") + + if italic: + styles.append("Italic") + + if not styles: + styles.append("Regular") + + style = " ".join(styles) + family = family.lower() + family_styles = cls.fonts.get(family) + if not family_styles: + return cls.default + + font = family_styles.get(style) + if font: + if font_size: + font = font.font_variant(size=font_size) + return font + + # Return first found + for font in family_styles: + if font_size: + font = font.font_variant(size=font_size) + return font + + return cls.default + + @classmethod + def load_fonts(cls): + + cls.default = ImageFont.load_default() + + available_font_ext = [".ttf", ".ttc"] + dirs = [] + if sys.platform == "win32": + # check the windows font repository + # NOTE: must use uppercase WINDIR, to work around bugs in + # 1.5.2's os.environ.get() + windir = os.environ.get("WINDIR") + if windir: + dirs.append(os.path.join(windir, "fonts")) + + elif sys.platform in ("linux", "linux2"): + lindirs = os.environ.get("XDG_DATA_DIRS", "") + if not lindirs: + # According to the freedesktop spec, XDG_DATA_DIRS should + # default to /usr/share + lindirs = "/usr/share" + dirs += [ + os.path.join(lindir, "fonts") for lindir in lindirs.split(":") + ] + + elif sys.platform == "darwin": + dirs += [ + "/Library/Fonts", + "/System/Library/Fonts", + os.path.expanduser("~/Library/Fonts") + ] + + available_fonts = collections.defaultdict(dict) + for directory in dirs: + for walkroot, walkdir, walkfilenames in os.walk(directory): + for walkfilename in walkfilenames: + ext = os.path.splitext(walkfilename)[1] + if ext.lower() not in available_font_ext: + continue + + fontpath = os.path.join(walkroot, walkfilename) + font_obj = ImageFont.truetype(fontpath) + family = font_obj.font.family.lower() + style = font_obj.font.style + available_fonts[family][style] = font_obj + + cls.fonts = available_fonts diff --git a/pype/scripts/slate/slate_base/items.py b/pype/scripts/slate/slate_base/items.py new file mode 100644 index 0000000000..ea31443f80 --- /dev/null +++ b/pype/scripts/slate/slate_base/items.py @@ -0,0 +1,666 @@ +import re +from PIL import Image + +from .base import BaseObj +from .font_factory import FontFactory + + +class BaseItem(BaseObj): + available_parents = ["main_frame", "layer"] + + @property + def item_pos_x(self): + if self.parent.obj_type == "main_frame": + return self._pos_x + return self.parent.child_pos_x(self.id) + + @property + def item_pos_y(self): + if self.parent.obj_type == "main_frame": + return self._pos_y + return self.parent.child_pos_y(self.id) + + def add_item(self, *args, **kwargs): + raise Exception("Can't add item to an item, use layers instead.") + + def draw(self, image, drawer): + raise NotImplementedError( + "Method `draw` is not implemented for <{}>".format( + self.__clas__.__name__ + ) + ) + + +class ItemImage(BaseItem): + obj_type = "image" + + def __init__(self, image_path, *args, **kwargs): + super(ItemImage, self).__init__(*args, **kwargs) + self.image_path = image_path + + def fill_data_format(self): + if re.match(self.fill_data_regex, self.image_path): + self.image_path = self.image_path.format(**self.fill_data) + + def draw(self, image, drawer): + source_image = Image.open(os.path.normpath(self.image_path)) + paste_image = source_image.resize( + (self.value_width(), self.value_height()), + Image.ANTIALIAS + ) + image.paste( + paste_image, + (self.value_pos_x, self.value_pos_y) + ) + + def value_width(self): + return int(self.style["width"]) + + def value_height(self): + return int(self.style["height"]) + + +class ItemRectangle(BaseItem): + obj_type = "rectangle" + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + fill = self.style.get("fill", False) + kwargs = {} + if fill: + kwargs["fill"] = bg_color + else: + kwargs["outline"] = bg_color + + start_pos_x = self.value_pos_x + start_pos_y = self.value_pos_y + end_pos_x = start_pos_x + self.value_width() + end_pos_y = start_pos_y + self.value_height() + drawer.rectangle( + ( + (start_pos_x, start_pos_y), + (end_pos_x, end_pos_y) + ), + **kwargs + ) + + def value_width(self): + return int(self.style["width"]) + + def value_height(self): + return int(self.style["height"]) + + +class ItemPlaceHolder(BaseItem): + obj_type = "placeholder" + + def __init__(self, image_path, *args, **kwargs): + self.image_path = image_path + super(ItemPlaceHolder, self).__init__(*args, **kwargs) + + def fill_data_format(self): + if re.match(self.fill_data_regex, self.image_path): + self.image_path = self.image_path.format(**self.fill_data) + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + + kwargs = {} + if bg_color != "tranparent": + kwargs["fill"] = bg_color + + start_pos_x = self.value_pos_x + start_pos_y = self.value_pos_y + end_pos_x = start_pos_x + self.value_width() + end_pos_y = start_pos_y + self.value_height() + + drawer.rectangle( + ( + (start_pos_x, start_pos_y), + (end_pos_x, end_pos_y) + ), + **kwargs + ) + + def value_width(self): + return int(self.style["width"]) + + def value_height(self): + return int(self.style["height"]) + + def collect_data(self): + return { + "pos_x": self.value_pos_x, + "pos_y": self.value_pos_y, + "width": self.value_width(), + "height": self.value_height(), + "path": self.image_path + } + + +class ItemText(BaseItem): + obj_type = "text" + + def __init__(self, value, *args, **kwargs): + super(ItemText, self).__init__(*args, **kwargs) + self.value = value + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + if bg_color and bg_color.lower() != "transparent": + # TODO border outline styles + drawer.rectangle( + (self.content_pos_start, self.content_pos_end), + fill=bg_color, + outline=None + ) + + font_color = self.style["font-color"] + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + drawer.text( + self.value_pos_start, + self.value, + font=font, + fill=font_color + ) + + def value_width(self): + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + width = font.getsize(self.value)[0] + return int(width) + + def value_height(self): + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + height = font.getsize(self.value)[1] + return int(height) + + +class ItemTable(BaseItem): + + obj_type = "table" + + def __init__(self, values, use_alternate_color=False, *args, **kwargs): + + self.values_by_cords = None + self.prepare_values(values) + + super(ItemTable, self).__init__(*args, **kwargs) + self.size_values = None + self.calculate_sizes() + + self.use_alternate_color = use_alternate_color + + def add_item(self, item): + if item.obj_type == "table-item": + return + super(ItemTable, self).add_item(item) + + def fill_data_format(self): + for item in self.values: + item.fill_data_format() + + def prepare_values(self, _values): + values = [] + values_by_cords = [] + row_count = 0 + col_count = 0 + for row in _values: + row_count += 1 + if len(row) > col_count: + col_count = len(row) + + for row_idx in range(row_count): + values_by_cords.append([]) + for col_idx in range(col_count): + values_by_cords[row_idx].append([]) + if col_idx <= len(_values[row_idx]) - 1: + col = _values[row_idx][col_idx] + else: + col = "" + + col_item = TableField(row_idx, col_idx, col, parent=self) + values_by_cords[row_idx][col_idx] = col_item + values.append(col_item) + + self.values = values + self.values_by_cords = values_by_cords + + def calculate_sizes(self): + row_heights = [] + col_widths = [] + for row_idx, row in enumerate(self.values_by_cords): + row_heights.append(0) + for col_idx, col_item in enumerate(row): + if len(col_widths) < col_idx + 1: + col_widths.append(0) + + _width = col_widths[col_idx] + item_width = col_item.width() + if _width < item_width: + col_widths[col_idx] = item_width + + _height = row_heights[row_idx] + item_height = col_item.height() + if _height < item_height: + row_heights[row_idx] = item_height + + self.size_values = (row_heights, col_widths) + + def draw(self, image, drawer): + bg_color = self.style["bg-color"] + if bg_color and bg_color.lower() != "transparent": + # TODO border outline styles + drawer.rectangle( + (self.content_pos_start, self.content_pos_end), + fill=bg_color, + outline=None + ) + + for value in self.values: + value.draw(image, drawer) + + def value_width(self): + row_heights, col_widths = self.size_values + width = 0 + for _width in col_widths: + width += _width + + if width != 0: + width -= 1 + return width + + def value_height(self): + row_heights, col_widths = self.size_values + height = 0 + for _height in row_heights: + height += _height + + if height != 0: + height -= 1 + return height + + def content_pos_info_by_cord(self, row_idx, col_idx): + row_heights, col_widths = self.size_values + pos_x = int(self.value_pos_x) + pos_y = int(self.value_pos_y) + width = 0 + height = 0 + for idx, value in enumerate(col_widths): + if col_idx == idx: + width = value + break + pos_x += value + + for idx, value in enumerate(row_heights): + if row_idx == idx: + height = value + break + pos_y += value + + return (pos_x, pos_y, width, height) + + +class TableField(BaseItem): + + obj_type = "table-item" + available_parents = ["table"] + ellide_text = "..." + + def __init__(self, row_idx, col_idx, value, *args, **kwargs): + super(TableField, self).__init__(*args, **kwargs) + self.row_idx = row_idx + self.col_idx = col_idx + self.value = value + + def recalculate_by_width(self, value, max_width): + padding = self.style["padding"] + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + + padding_right = self.style.get("padding-right") + if padding_right is None: + padding_right = padding + + max_width -= (padding_left + padding_right) + + if not value: + return "" + + word_wrap = self.style.get("word-wrap") + ellide = self.style.get("ellide") + max_lines = self.style.get("max-lines") + + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + val_width = font.getsize(value)[0] + if val_width <= max_width: + return value + + if not ellide and not word_wrap: + # TODO logging + self.log.warning(( + "Can't draw text because is too long with" + " `word-wrap` and `ellide` turned off <{}>" + ).format(value)) + return "" + + elif ellide and not word_wrap: + max_lines = 1 + + words = [word for word in value.split()] + words_len = len(words) + lines = [] + last_index = None + while True: + start_index = 0 + if last_index is not None: + start_index = int(last_index) + 1 + + line = "" + for idx in range(start_index, words_len): + _word = words[idx] + connector = " " + if line == "": + connector = "" + + _line = connector.join([line, _word]) + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + break + line = _line + last_index = idx + + if line: + lines.append(line) + + if last_index == words_len - 1: + break + + elif last_index is None: + add_message = "" + if ellide: + add_message = " String was shortened to `{}`." + line = "" + for idx, char in enumerate(words[idx]): + _line = line + char + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = line + char + + lines.append(line) + # TODO logging + self.log.warning(( + "Font size is too big.{} <{}>" + ).format(add_message, value)) + break + + output = "" + if not lines: + return output + + over_max_lines = (max_lines and len(lines) > max_lines) + if not over_max_lines: + return "\n".join([line for line in lines]) + + lines = [lines[idx] for idx in range(max_lines)] + if not ellide: + return "\n".join(lines) + + last_line = lines[-1] + last_line_width = font.getsize(last_line + self.ellide_text)[0] + if last_line_width <= max_width: + lines[-1] += self.ellide_text + return "\n".join([line for line in lines]) + + last_line_words = last_line.split() + if len(last_line_words) == 1: + if max_lines > 1: + # TODO try previous line? + lines[-1] = self.ellide_text + return "\n".join([line for line in lines]) + + line = "" + for idx, word in enumerate(last_line_words): + _line = line + word + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = _line + lines[-1] = line + + return "\n".join([line for line in lines]) + + line = "" + for idx, _word in enumerate(last_line_words): + connector = " " + if line == "": + connector = "" + + _line = connector.join([line, _word + self.ellide_text]) + _line_width = font.getsize(_line)[0] + + if _line_width <= max_width: + line = connector.join([line, _word]) + continue + + if idx != 0: + line += self.ellide_text + break + + if max_lines != 1: + # TODO try previous line? + line = self.ellide_text + break + + for idx, char in enumerate(_word): + _line = line + char + self.ellide_text + _line_width = font.getsize(_line)[0] + if _line_width > max_width: + if idx == 0: + line = _line + break + line = line + char + break + + lines[-1] = line + + return "\n".join([line for line in lines]) + + def fill_data_format(self): + value = self.value + if re.match(self.fill_data_regex, value): + value = value.format(**self.fill_data) + + self.orig_value = value + + max_width = self.style.get("max-width") + max_width = self.style.get("width") or max_width + if max_width: + value = self.recalculate_by_width(value, max_width) + + self.value = value + + def content_width(self): + width = self.style.get("width") + if width: + return int(width) + return super(TableField, self).content_width() + + def content_height(self): + return super(TableField, self).content_height() + + def value_width(self): + if not self.value: + return 0 + + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + width = font.getsize_multiline(self.value)[0] + 1 + + min_width = self.style.get("min-height") + if min_width and min_width > width: + width = min_width + + return int(width) + + def value_height(self): + if not self.value: + return 0 + + height = self.style.get("height") + if height: + return int(height) + + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + height = font.getsize_multiline(self.value)[1] + 1 + + min_height = self.style.get("min-height") + if min_height and min_height > height: + height = min_height + + return int(height) + + @property + def item_pos_x(self): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) + ) + return pos_x + + @property + def item_pos_y(self): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) + ) + return pos_y + + @property + def value_pos_x(self): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) + ) + alignment_hor = self.style["alignment-horizontal"].lower() + if alignment_hor in ["center", "centre"]: + pos_x += (width - self.value_width()) / 2 + + elif alignment_hor == "right": + pos_x += width - self.value_width() + + else: + padding = self.style["padding"] + padding_left = self.style.get("padding-left") + if padding_left is None: + padding_left = padding + + pos_x += padding_left + + return int(pos_x) + + @property + def value_pos_y(self): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) + ) + + alignment_ver = self.style["alignment-vertical"].lower() + if alignment_ver in ["center", "centre"]: + pos_y += (height - self.value_height()) / 2 + + elif alignment_ver == "bottom": + pos_y += height - self.value_height() + + else: + padding = self.style["padding"] + padding_top = self.style.get("padding-top") + if padding_top is None: + padding_top = padding + + pos_y += padding_top + + return int(pos_y) + + def draw(self, image, drawer): + pos_x, pos_y, width, height = ( + self.parent.content_pos_info_by_cord(self.row_idx, self.col_idx) + ) + pos_start = (pos_x, pos_y) + pos_end = (pos_x + width, pos_y + height) + bg_color = self.style["bg-color"] + if self.parent.use_alternate_color and (self.row_idx % 2) == 1: + bg_color = self.style["bg-alter-color"] + + if bg_color and bg_color.lower() != "transparent": + # TODO border outline styles + drawer.rectangle( + (pos_start, pos_end), + fill=bg_color, + outline=None + ) + + font_color = self.style["font-color"] + font_family = self.style["font-family"] + font_size = self.style["font-size"] + font_bold = self.style.get("font-bold", False) + font_italic = self.style.get("font-italic", False) + + font = FontFactory.get_font( + font_family, font_size, font_italic, font_bold + ) + + alignment_hor = self.style["alignment-horizontal"].lower() + if alignment_hor == "centre": + alignment_hor = "center" + + drawer.multiline_text( + self.value_pos_start, + self.value, + font=font, + fill=font_color, + align=alignment_hor + ) diff --git a/pype/scripts/slate/slate_base/layer.py b/pype/scripts/slate/slate_base/layer.py new file mode 100644 index 0000000000..ea3a3de53e --- /dev/null +++ b/pype/scripts/slate/slate_base/layer.py @@ -0,0 +1,139 @@ +from .base import BaseObj + + +class Layer(BaseObj): + obj_type = "layer" + available_parents = ["main_frame", "layer"] + + # Direction can be 0=vertical/ 1=horizontal + def __init__(self, direction=0, *args, **kwargs): + super(Layer, self).__init__(*args, **kwargs) + self._direction = direction + + @property + def item_pos_x(self): + if self.parent.obj_type == self.obj_type: + pos_x = self.parent.child_pos_x(self.id) + elif self.parent.obj_type == "main_frame": + pos_x = self._pos_x + else: + pos_x = self.parent.value_pos_x + return int(pos_x) + + @property + def item_pos_y(self): + if self.parent.obj_type == self.obj_type: + pos_y = self.parent.child_pos_y(self.id) + elif self.parent.obj_type == "main_frame": + pos_y = self._pos_y + else: + pos_y = self.parent.value_pos_y + + return int(pos_y) + + @property + def direction(self): + if self._direction not in (0, 1): + self.log.warning(( + "Direction of Layer must be 0 or 1 " + "(0 is horizontal / 1 is vertical)! Setting to 0." + )) + return 0 + return self._direction + + def child_pos_x(self, item_id): + pos_x = self.value_pos_x + alignment_hor = self.style["alignment-horizontal"].lower() + + item = None + for id, _item in self.items.items(): + if item_id == id: + item = _item + break + + if self.direction == 1: + for id, _item in self.items.items(): + if item_id == id: + break + + pos_x += _item.width() + if _item.obj_type not in ["image", "placeholder"]: + pos_x += 1 + + else: + if alignment_hor in ["center", "centre"]: + pos_x += (self.content_width() - item.content_width()) / 2 + + elif alignment_hor == "right": + pos_x += self.content_width() - item.content_width() + + else: + margin = self.style["margin"] + margin_left = self.style.get("margin-left") or margin + pos_x += margin_left + + return int(pos_x) + + def child_pos_y(self, item_id): + pos_y = self.value_pos_y + alignment_ver = self.style["alignment-horizontal"].lower() + + item = None + for id, _item in self.items.items(): + if item_id == id: + item = _item + break + + if self.direction != 1: + for id, item in self.items.items(): + if item_id == id: + break + pos_y += item.height() + if item.obj_type not in ["image", "placeholder"]: + pos_y += 1 + + else: + if alignment_ver in ["center", "centre"]: + pos_y += (self.content_height() - item.content_height()) / 2 + + elif alignment_ver == "bottom": + pos_y += self.content_height() - item.content_height() + + return int(pos_y) + + def value_height(self): + height = 0 + for item in self.items.values(): + if self.direction == 1: + if height > item.height(): + continue + # times 1 because won't get object pointer but number + height = item.height() + else: + height += item.height() + + # TODO this is not right + min_height = self.style.get("min-height") + if min_height and min_height > height: + return min_height + return height + + def value_width(self): + width = 0 + for item in self.items.values(): + if self.direction == 0: + if width > item.width(): + continue + # times 1 because won't get object pointer but number + width = item.width() + else: + width += item.width() + + min_width = self.style.get("min-width") + if min_width and min_width > width: + return min_width + return width + + def draw(self, image, drawer): + for item in self.items.values(): + item.draw(image, drawer) diff --git a/pype/scripts/slate/slate_base/main_frame.py b/pype/scripts/slate/slate_base/main_frame.py new file mode 100644 index 0000000000..837e752aae --- /dev/null +++ b/pype/scripts/slate/slate_base/main_frame.py @@ -0,0 +1,77 @@ +import os +import re +from PIL import Image, ImageDraw + +from .base import BaseObj + + +class MainFrame(BaseObj): + + obj_type = "main_frame" + available_parents = [None] + + def __init__( + self, width, height, destination_path, fill_data={}, *args, **kwargs + ): + kwargs["parent"] = None + super(MainFrame, self).__init__(*args, **kwargs) + self._width = width + self._height = height + self.dst_path = destination_path + self._fill_data = fill_data + self.fill_data_format() + + def fill_data_format(self): + if re.match(self.fill_data_regex, self.dst_path): + self.dst_path = self.dst_path.format(**self.fill_data) + + @property + def fill_data(self): + return self._fill_data + + def value_width(self): + width = 0 + for item in self.items.values(): + width += item.width() + return width + + def value_height(self): + height = 0 + for item in self.items.values(): + height += item.height() + return height + + def width(self): + return self._width + + def height(self): + return self._height + + def draw(self, path=None): + dir_path = os.path.dirname(self.dst_path) + if not os.path.exists(dir_path): + os.makedirs(dir_path) + + bg_color = self.style["bg-color"] + image = Image.new("RGB", (self.width(), self.height()), color=bg_color) + drawer = ImageDraw.Draw(image) + for item in self.items.values(): + item.draw(image, drawer) + + image.save(self.dst_path) + self.reset() + + def collect_data(self): + output = {} + output["width"] = self.width() + output["height"] = self.height() + output["slate_path"] = self.dst_path + + placeholders = self.find_item(obj_type="placeholder") + placeholders_data = [] + for placeholder in placeholders: + placeholders_data.append(placeholder.collect_data()) + + output["placeholders"] = placeholders_data + + return output diff --git a/pype/scripts/slate/style_schema.json b/pype/scripts/slate/style_schema.json deleted file mode 100644 index dab6151df5..0000000000 --- a/pype/scripts/slate/style_schema.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "alignment": { - "description": "Alignment of item", - "type": "string", - "enum": ["left", "right", "center"], - "example": "left" - }, - "font-family": { - "description": "Font type", - "type": "string", - "example": "Arial" - }, - "font-size": { - "description": "Font size", - "type": "integer", - "example": 26 - }, - "font-color": { - "description": "Font color", - "type": "string", - "regex": ( - "^[#]{1}[a-fA-F0-9]{1}$" - " | ^[#]{1}[a-fA-F0-9]{3}$" - " | ^[#]{1}[a-fA-F0-9]{6}$" - ), - "example": "#ffffff" - }, - "font-bold": { - "description": "Font boldness", - "type": "boolean", - "example": True - }, - "bg-color": { - "description": "Background color, None means transparent", - "type": ["string", None], - "regex": ( - "^[#]{1}[a-fA-F0-9]{1}$" - " | ^[#]{1}[a-fA-F0-9]{3}$" - " | ^[#]{1}[a-fA-F0-9]{6}$" - ), - "example": "#333" - }, - "bg-alter-color": None, -} From f54a3e981a12ff078fa70b0e6f8fa7f5e55a79a2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 17:07:59 +0100 Subject: [PATCH 33/42] created basic slate usage --- pype/scripts/slate/lib.py | 87 ++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/pype/scripts/slate/lib.py b/pype/scripts/slate/lib.py index ca3c0f2e41..750046269e 100644 --- a/pype/scripts/slate/lib.py +++ b/pype/scripts/slate/lib.py @@ -1,26 +1,46 @@ import os import json +import logging from queue import Queue -# --- Lines for debug purpose --------------------------------- -import sys -sys.path.append(r"C:\Users\Public\pype_env2\Lib\site-packages") -# ------------------------------------------------------------- - -from slate_base.main_frame import MainFrame -from slate_base.layer import Layer -from slate_base.items import ( +from .slate_base.main_frame import MainFrame +from .slate_base.layer import Layer +from .slate_base.items import ( ItemTable, ItemImage, ItemRectangle, ItemPlaceHolder ) +from pypeapp import config -def main(fill_data): - cur_folder = os.path.dirname(os.path.abspath(__file__)) - input_json = os.path.join(cur_folder, "netflix_v03.json") - with open(input_json) as json_file: - slate_data = json.load(json_file) +log = logging.getLogger(__name__) +RequiredSlateKeys = ["width", "height", "destination_path"] + + +def create_slates(fill_data, slate_name): + presets = config.get_presets() + slate_presets = ( + presets + .get("tools", {}) + .get("slates") + ) or {} + slate_data = slate_presets.get(slate_name) + + if not slate_data: + log.error("Slate data of <{}> does not exists.") + return False + + missing_keys = [] + for key in RequiredSlateKeys: + if key not in slate_data: + missing_keys.append("`{}`".format(key)) + + if missing_keys: + log.error("Slate data of <{}> miss required keys: {}".format( + slate_name, ", ".join(missing_keys) + )) + return False + width = slate_data["width"] height = slate_data["height"] dst_path = slate_data["destination_path"] @@ -44,7 +64,7 @@ def main(fill_data): if parent.obj_type != "main_frame": if pos_x or pos_y: # TODO logging - self.log.warning(( + log.warning(( "You have specified `pos_x` and `pos_y` but won't be used." " Possible only if parent of an item is `main_frame`." )) @@ -83,32 +103,43 @@ def main(fill_data): else: # TODO logging - self.log.warning( - "Slate item not implemented <{}> - skipping".format(item_type) + log.warning( + "Not implemented object type `{}` - skipping".format(item_type) ) main.draw() - print("Slate creation finished") + log.debug("Slate creation finished") -if __name__ == "__main__": +def example(): fill_data = { - "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v03.png", + "destination_path": "PATH/TO/OUTPUT/FILE", "project": { - "name": "Project name" + "name": "Testing project" }, "intent": "WIP", - "version_name": "mei_101_001_0020_slate_NFX_v001", + "version_name": "seq01_sh0100_compositing_v01", "date": "2019-08-09", "shot_type": "2d comp", - "submission_note": "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production.", - "thumbnail_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", - "color_bar_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", - "vendor": "DAZZLE", - "shot_name": "SLATE_SIMPLE", + "submission_note": ( + "Lorem ipsum dolor sit amet, consectetuer adipiscing elit." + " Aenean commodo ligula eget dolor. Aenean massa." + " Cum sociis natoque penatibus et magnis dis parturient montes," + " nascetur ridiculus mus. Donec quam felis, ultricies nec," + " pellentesque eu, pretium quis, sem. Nulla consequat massa quis" + " enim. Donec pede justo, fringilla vel," + " aliquet nec, vulputate eget, arcu." + ), + "thumbnail_path": "PATH/TO/THUMBNAIL/FILE", + "color_bar_path": "PATH/TO/COLOR/BAR/FILE", + "vendor": "Our Studio", + "shot_name": "sh0100", "frame_start": 1001, "frame_end": 1004, "duration": 3 } - main(fill_data) - # raise NotImplementedError("Slates don't have Implemented args running") + create_slates(fill_data, "example_HD") + + +if __name__ == "__main__": + raise NotImplementedError("Slates don't have Implemented args running") From 5b0e8fe8d0dac8c8e8a70e3557cc53a866932c7f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 17:08:17 +0100 Subject: [PATCH 34/42] remove netflix examples --- pype/scripts/slate/netflix_v01.json | 201 -------------------------- pype/scripts/slate/netflix_v02.json | 213 ---------------------------- pype/scripts/slate/netflix_v03.json | 213 ---------------------------- 3 files changed, 627 deletions(-) delete mode 100644 pype/scripts/slate/netflix_v01.json delete mode 100644 pype/scripts/slate/netflix_v02.json delete mode 100644 pype/scripts/slate/netflix_v03.json diff --git a/pype/scripts/slate/netflix_v01.json b/pype/scripts/slate/netflix_v01.json deleted file mode 100644 index 9a5974839d..0000000000 --- a/pype/scripts/slate/netflix_v01.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "width": 1920, - "height": 1080, - "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v01.png", - "style": { - "*": { - "font-family": "arial", - "font-size": 26, - "font-color": "#ffffff", - "font-bold": false, - "font-italic": false, - "bg-color": "#0077ff", - "alignment-horizontal": "left", - "alignment-vertical": "top", - "padding": 0, - "margin": 0 - }, - "layer": { - "padding": 0, - "margin": 0 - }, - "rectangle": { - "padding": 0, - "margin": 0, - "bg-color": "#E9324B", - "fill": true - }, - "main_frame": { - "bg-color": "#252525" - }, - "table": { - "bg-color": "transparent" - }, - "table-item": { - "bg-color": "#212121", - "bg-alter-color": "#272727", - "font-color": "#dcdcdc", - "font-bold": false, - "font-italic": false, - "padding": 5, - "padding-bottom": 10, - "alignment-horizontal": "left", - "alignment-vertical": "top", - "word-wrap": false, - "ellide": true, - "max-lines": 1 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "font-bold": true, - "ellide": false, - "word-wrap": true, - "max-lines": null - }, - "table-item-col[1]": { - "font-size": 40, - "padding-left": 10 - }, - "#colorbar": { - "bg-color": "#9932CC" - } - }, - "items": [{ - "type": "layer", - "name": "LeftSide", - "pos_x": 27, - "pos_y": 27, - "style": { - "width": 1094, - "height": 1000 - }, - "items": [{ - "type": "layer", - "direction": 1, - "style": { - "table-item": { - "bg-color": "transparent" - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "alignment-horizontal": "right" - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "font-bold": true, - "font-size": 40 - } - }, - "items": [{ - "type": "table", - "values": [ - ["Show:", "First Contact"] - ], - "style": { - "table-item-field[0:0]": { - "width": 150 - }, - "table-item-field[1:0]": { - "width": 580 - } - } - }, { - "type": "table", - "values": [ - ["Submitting For:", "SAMPLE"] - ], - "style": { - "table-item-field[0:0]": { - "width": 160 - }, - "table-item-field[1:0]": { - "width": 218, - "alignment-horizontal": "right" - } - } - }] - }, { - "type": "rectangle", - "style": { - "bg-color": "#bc1015", - "width": 1108, - "height": 5, - "fill": true - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Version name:", "mei_101_001_0020_slate_NFX_v001"], - ["Date:", "2019-08-09"], - ["Shot Types:", "2d comp"], - ["Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production."] - ], - "style": { - "table-item": { - "padding-bottom": 20 - }, - "table-item-field[1:0]": { - "font-bold": true - }, - "table-item-field[1:3]": { - "word-wrap": true, - "ellide": true, - "max-lines": 2 - }, - "table-item-col[0]": { - "alignment-horizontal": "right", - "width": 150 - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "width": 958 - } - } - }] - }, { - "type": "layer", - "name": "RightSide", - "pos_x": 1164, - "pos_y": 26, - "items": [{ - "type": "placeholder", - "name": "thumbnail", - "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", - "style": { - "width": 730, - "height": 412 - } - }, { - "type": "placeholder", - "name": "colorbar", - "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", - "return_data": true, - "style": { - "width": 730, - "height": 55 - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Vendor:", "DAZZLE"], - ["Shot Name:", "SLATE_SIMPLE"], - ["Frames:", "0 - 1 (2)"] - ], - "style": { - "table-item-col[0]": { - "alignment-horizontal": "left", - "width": 200 - }, - "table-item-col[1]": { - "alignment-horizontal": "right", - "width": 530, - "font-size": 30 - } - } - }] - }] -} diff --git a/pype/scripts/slate/netflix_v02.json b/pype/scripts/slate/netflix_v02.json deleted file mode 100644 index f373ed8134..0000000000 --- a/pype/scripts/slate/netflix_v02.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "width": 1920, - "height": 1080, - "destination_path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/netflix_output_v02.png", - "style": { - "*": { - "font-family": "arial", - "font-size": 26, - "font-color": "#ffffff", - "font-bold": false, - "font-italic": false, - "bg-color": "#0077ff", - "alignment-horizontal": "left", - "alignment-vertical": "top" - }, - "layer": { - "padding": 0, - "margin": 0 - }, - "rectangle": { - "padding": 0, - "margin": 0, - "bg-color": "#E9324B", - "fill": true - }, - "main_frame": { - "padding": 0, - "margin": 0, - "bg-color": "#252525" - }, - "table": { - "padding": 0, - "margin": 0, - "bg-color": "transparent" - }, - "table-item": { - "padding": 5, - "padding-bottom": 10, - "margin": 0, - "bg-color": "#212121", - "bg-alter-color": "#272727", - "font-color": "#dcdcdc", - "font-bold": false, - "font-italic": false, - "alignment-horizontal": "left", - "alignment-vertical": "top", - "word-wrap": false, - "ellide": true, - "max-lines": 1 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "font-bold": true, - "ellide": false, - "word-wrap": true, - "max-lines": null - }, - "table-item-col[1]": { - "font-size": 40, - "padding-left": 10 - }, - "#colorbar": { - "bg-color": "#9932CC" - } - }, - "items": [{ - "type": "layer", - "direction": 1, - "name": "MainLayer", - "style": { - "#MainLayer": { - "width": 1094, - "height": 1000, - "margin": 25, - "padding": 0 - }, - "#LeftSide": { - "margin-right": 25 - } - }, - "items": [{ - "type": "layer", - "name": "LeftSide", - "items": [{ - "type": "layer", - "direction": 1, - "style": { - "table-item": { - "bg-color": "transparent", - "padding-bottom": 20 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "alignment-horizontal": "right" - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "font-bold": true, - "font-size": 40 - } - }, - "items": [{ - "type": "table", - "values": [ - ["Show:", "First Contact"] - ], - "style": { - "table-item-field[0:0]": { - "width": 150 - }, - "table-item-field[1:0]": { - "width": 580 - } - } - }, { - "type": "table", - "values": [ - ["Submitting For:", "SAMPLE"] - ], - "style": { - "table-item-field[0:0]": { - "width": 160 - }, - "table-item-field[1:0]": { - "width": 218, - "alignment-horizontal": "right" - } - } - }] - }, { - "type": "rectangle", - "style": { - "bg-color": "#bc1015", - "width": 1108, - "height": 5, - "fill": true - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Version name:", "mei_101_001_0020_slate_NFX_v001"], - ["Date:", "2019-08-09"], - ["Shot Types:", "2d comp"], - ["Submission Note:", "Submitting as and example with all MEI fields filled out. As well as the additional fields Shot description, Episode, Scene, and Version # that were requested by production."] - ], - "style": { - "table-item": { - "padding-bottom": 20 - }, - "table-item-field[1:0]": { - "font-bold": true - }, - "table-item-field[1:3]": { - "word-wrap": true, - "ellide": true, - "max-lines": 2 - }, - "table-item-col[0]": { - "alignment-horizontal": "right", - "width": 150 - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "width": 958 - } - } - }] - }, { - "type": "layer", - "name": "RightSide", - "items": [{ - "type": "placeholder", - "name": "thumbnail", - "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/birds.png", - "style": { - "width": 730, - "height": 412 - } - }, { - "type": "placeholder", - "name": "colorbar", - "path": "C:/Users/jakub.trllo/Desktop/Tests/files/image/kitten.jpg", - "return_data": true, - "style": { - "width": 730, - "height": 55 - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Vendor:", "DAZZLE"], - ["Shot Name:", "SLATE_SIMPLE"], - ["Frames:", "0 - 1 (2)"] - ], - "style": { - "table-item-col[0]": { - "alignment-horizontal": "left", - "width": 200 - }, - "table-item-col[1]": { - "alignment-horizontal": "right", - "width": 530, - "font-size": 30 - } - } - }] - }] - }] -} diff --git a/pype/scripts/slate/netflix_v03.json b/pype/scripts/slate/netflix_v03.json deleted file mode 100644 index 975cb60133..0000000000 --- a/pype/scripts/slate/netflix_v03.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "width": 1920, - "height": 1080, - "destination_path": "{destination_path}", - "style": { - "*": { - "font-family": "arial", - "font-size": 26, - "font-color": "#ffffff", - "font-bold": false, - "font-italic": false, - "bg-color": "#0077ff", - "alignment-horizontal": "left", - "alignment-vertical": "top" - }, - "layer": { - "padding": 0, - "margin": 0 - }, - "rectangle": { - "padding": 0, - "margin": 0, - "bg-color": "#E9324B", - "fill": true - }, - "main_frame": { - "padding": 0, - "margin": 0, - "bg-color": "#252525" - }, - "table": { - "padding": 0, - "margin": 0, - "bg-color": "transparent" - }, - "table-item": { - "padding": 5, - "padding-bottom": 10, - "margin": 0, - "bg-color": "#212121", - "bg-alter-color": "#272727", - "font-color": "#dcdcdc", - "font-bold": false, - "font-italic": false, - "alignment-horizontal": "left", - "alignment-vertical": "top", - "word-wrap": false, - "ellide": true, - "max-lines": 1 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "font-bold": true, - "ellide": false, - "word-wrap": true, - "max-lines": null - }, - "table-item-col[1]": { - "font-size": 40, - "padding-left": 10 - }, - "#colorbar": { - "bg-color": "#9932CC" - } - }, - "items": [{ - "type": "layer", - "direction": 1, - "name": "MainLayer", - "style": { - "#MainLayer": { - "width": 1094, - "height": 1000, - "margin": 25, - "padding": 0 - }, - "#LeftSide": { - "margin-right": 25 - } - }, - "items": [{ - "type": "layer", - "name": "LeftSide", - "items": [{ - "type": "layer", - "direction": 1, - "style": { - "table-item": { - "bg-color": "transparent", - "padding-bottom": 20 - }, - "table-item-col[0]": { - "font-size": 20, - "font-color": "#898989", - "alignment-horizontal": "right" - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "font-bold": true, - "font-size": 40 - } - }, - "items": [{ - "type": "table", - "values": [ - ["Show:", "{project[name]}"] - ], - "style": { - "table-item-field[0:0]": { - "width": 150 - }, - "table-item-field[1:0]": { - "width": 580 - } - } - }, { - "type": "table", - "values": [ - ["Submitting For:", "{intent}"] - ], - "style": { - "table-item-field[0:0]": { - "width": 160 - }, - "table-item-field[1:0]": { - "width": 218, - "alignment-horizontal": "right" - } - } - }] - }, { - "type": "rectangle", - "style": { - "bg-color": "#bc1015", - "width": 1108, - "height": 5, - "fill": true - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Version name:", "{version_name}"], - ["Date:", "{date}"], - ["Shot Types:", "{shot_type}"], - ["Submission Note:", "{submission_note}"] - ], - "style": { - "table-item": { - "padding-bottom": 20 - }, - "table-item-field[1:0]": { - "font-bold": true - }, - "table-item-field[1:3]": { - "word-wrap": true, - "ellide": true, - "max-lines": 4 - }, - "table-item-col[0]": { - "alignment-horizontal": "right", - "width": 150 - }, - "table-item-col[1]": { - "alignment-horizontal": "left", - "width": 958 - } - } - }] - }, { - "type": "layer", - "name": "RightSide", - "items": [{ - "type": "placeholder", - "name": "thumbnail", - "path": "{thumbnail_path}", - "style": { - "width": 730, - "height": 412 - } - }, { - "type": "placeholder", - "name": "colorbar", - "path": "{color_bar_path}", - "return_data": true, - "style": { - "width": 730, - "height": 55 - } - }, { - "type": "table", - "use_alternate_color": true, - "values": [ - ["Vendor:", "{vendor}"], - ["Shot Name:", "{shot_name}"], - ["Frames:", "{frame_start} - {frame_end} ({duration})"] - ], - "style": { - "table-item-col[0]": { - "alignment-horizontal": "left", - "width": 200 - }, - "table-item-col[1]": { - "alignment-horizontal": "right", - "width": 530, - "font-size": 30 - } - } - }] - }] - }] -} From b3f4673248a8ec06dbc44d6457dbb095688aaa20 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 17:09:16 +0100 Subject: [PATCH 35/42] renamed slate folder to slates folder --- pype/scripts/{slate => slates}/lib.py | 0 pype/scripts/{slate => slates}/slate_base/__init__.py | 0 pype/scripts/{slate => slates}/slate_base/base.py | 0 pype/scripts/{slate => slates}/slate_base/default_style.json | 0 pype/scripts/{slate => slates}/slate_base/font_factory.py | 0 pype/scripts/{slate => slates}/slate_base/items.py | 0 pype/scripts/{slate => slates}/slate_base/layer.py | 0 pype/scripts/{slate => slates}/slate_base/main_frame.py | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename pype/scripts/{slate => slates}/lib.py (100%) rename pype/scripts/{slate => slates}/slate_base/__init__.py (100%) rename pype/scripts/{slate => slates}/slate_base/base.py (100%) rename pype/scripts/{slate => slates}/slate_base/default_style.json (100%) rename pype/scripts/{slate => slates}/slate_base/font_factory.py (100%) rename pype/scripts/{slate => slates}/slate_base/items.py (100%) rename pype/scripts/{slate => slates}/slate_base/layer.py (100%) rename pype/scripts/{slate => slates}/slate_base/main_frame.py (100%) diff --git a/pype/scripts/slate/lib.py b/pype/scripts/slates/lib.py similarity index 100% rename from pype/scripts/slate/lib.py rename to pype/scripts/slates/lib.py diff --git a/pype/scripts/slate/slate_base/__init__.py b/pype/scripts/slates/slate_base/__init__.py similarity index 100% rename from pype/scripts/slate/slate_base/__init__.py rename to pype/scripts/slates/slate_base/__init__.py diff --git a/pype/scripts/slate/slate_base/base.py b/pype/scripts/slates/slate_base/base.py similarity index 100% rename from pype/scripts/slate/slate_base/base.py rename to pype/scripts/slates/slate_base/base.py diff --git a/pype/scripts/slate/slate_base/default_style.json b/pype/scripts/slates/slate_base/default_style.json similarity index 100% rename from pype/scripts/slate/slate_base/default_style.json rename to pype/scripts/slates/slate_base/default_style.json diff --git a/pype/scripts/slate/slate_base/font_factory.py b/pype/scripts/slates/slate_base/font_factory.py similarity index 100% rename from pype/scripts/slate/slate_base/font_factory.py rename to pype/scripts/slates/slate_base/font_factory.py diff --git a/pype/scripts/slate/slate_base/items.py b/pype/scripts/slates/slate_base/items.py similarity index 100% rename from pype/scripts/slate/slate_base/items.py rename to pype/scripts/slates/slate_base/items.py diff --git a/pype/scripts/slate/slate_base/layer.py b/pype/scripts/slates/slate_base/layer.py similarity index 100% rename from pype/scripts/slate/slate_base/layer.py rename to pype/scripts/slates/slate_base/layer.py diff --git a/pype/scripts/slate/slate_base/main_frame.py b/pype/scripts/slates/slate_base/main_frame.py similarity index 100% rename from pype/scripts/slate/slate_base/main_frame.py rename to pype/scripts/slates/slate_base/main_frame.py From 16bc4dcc6098dd1e514aac06851a83630a7fdfef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Jan 2020 17:16:02 +0100 Subject: [PATCH 36/42] exchange col_idx and row_idx for field style --- pype/scripts/slates/slate_base/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pype/scripts/slates/slate_base/base.py b/pype/scripts/slates/slate_base/base.py index 35a3b6af6d..35ef46769c 100644 --- a/pype/scripts/slates/slate_base/base.py +++ b/pype/scripts/slates/slate_base/base.py @@ -213,7 +213,7 @@ class BaseObj: result = re.search(field_regex, key) if result: - col_idx, row_idx = get_indexes_from_regex_match( + row_idx, col_idx = get_indexes_from_regex_match( result, True ) if self.col_idx == col_idx and self.row_idx == row_idx: From f72a19457bafe003ebc1c7a5deb12fdfbeb414bc Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Jan 2020 10:28:54 +0100 Subject: [PATCH 37/42] added example for testing --- pype/scripts/slates/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pype/scripts/slates/lib.py b/pype/scripts/slates/lib.py index 750046269e..a73f87e82f 100644 --- a/pype/scripts/slates/lib.py +++ b/pype/scripts/slates/lib.py @@ -1,5 +1,3 @@ -import os -import json import logging from queue import Queue @@ -112,6 +110,10 @@ def create_slates(fill_data, slate_name): def example(): + # import sys + # sys.append(r"PATH/TO/PILLOW/PACKAGE") + # sys.append(r"PATH/TO/PYPE-SETUP") + fill_data = { "destination_path": "PATH/TO/OUTPUT/FILE", "project": { @@ -142,4 +144,4 @@ def example(): if __name__ == "__main__": - raise NotImplementedError("Slates don't have Implemented args running") + example() From 599a227359e1bc57271215441af8e22f957bdd90 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Mar 2020 13:18:26 +0100 Subject: [PATCH 38/42] fix queue import --- pype/scripts/slates/lib.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pype/scripts/slates/lib.py b/pype/scripts/slates/lib.py index a73f87e82f..154c689349 100644 --- a/pype/scripts/slates/lib.py +++ b/pype/scripts/slates/lib.py @@ -1,5 +1,9 @@ import logging -from queue import Queue + +try: + from queue import Queue +except Exception: + from Queue import Queue from .slate_base.main_frame import MainFrame from .slate_base.layer import Layer From 9ca3541c827353f4da8656853f6469f2ea7ab320 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Mar 2020 15:30:46 +0100 Subject: [PATCH 39/42] slates are more like package, fixed few bugs and example is in specific file with more data of example --- pype/scripts/slates/__init__.py | 2 + pype/scripts/slates/__main__.py | 10 + pype/scripts/slates/slate_base/api.py | 15 ++ pype/scripts/slates/slate_base/example.py | 254 ++++++++++++++++++++ pype/scripts/slates/slate_base/items.py | 1 + pype/scripts/slates/{ => slate_base}/lib.py | 69 ++---- 6 files changed, 300 insertions(+), 51 deletions(-) create mode 100644 pype/scripts/slates/__init__.py create mode 100644 pype/scripts/slates/__main__.py create mode 100644 pype/scripts/slates/slate_base/api.py create mode 100644 pype/scripts/slates/slate_base/example.py rename pype/scripts/slates/{ => slate_base}/lib.py (63%) diff --git a/pype/scripts/slates/__init__.py b/pype/scripts/slates/__init__.py new file mode 100644 index 0000000000..52937708ea --- /dev/null +++ b/pype/scripts/slates/__init__.py @@ -0,0 +1,2 @@ +from . import slate_base +from .slate_base import api diff --git a/pype/scripts/slates/__main__.py b/pype/scripts/slates/__main__.py new file mode 100644 index 0000000000..29282d3226 --- /dev/null +++ b/pype/scripts/slates/__main__.py @@ -0,0 +1,10 @@ +from slate_base import api + + +def main(in_args=None): + # TODO proper argument handling + api.example() + + +if __name__ == "__main__": + main() diff --git a/pype/scripts/slates/slate_base/api.py b/pype/scripts/slates/slate_base/api.py new file mode 100644 index 0000000000..cd64c68134 --- /dev/null +++ b/pype/scripts/slates/slate_base/api.py @@ -0,0 +1,15 @@ +from .font_factory import FontFactory +from .base import BaseObj, load_default_style +from .main_frame import MainFrame +from .layer import Layer +from .items import ( + BaseItem, + ItemImage, + ItemRectangle, + ItemPlaceHolder, + ItemText, + ItemTable, + TableField +) +from .lib import create_slates +from .example import example diff --git a/pype/scripts/slates/slate_base/example.py b/pype/scripts/slates/slate_base/example.py new file mode 100644 index 0000000000..560f9ec02d --- /dev/null +++ b/pype/scripts/slates/slate_base/example.py @@ -0,0 +1,254 @@ +# import sys +# sys.append(r"PATH/TO/PILLOW/PACKAGE") + +from . import api + + +def example(): + """Example data to demontrate function. + + It is required to fill "destination_path", "thumbnail_path" + and "color_bar_path" in `example_fill_data` to be able to execute. + """ + + example_fill_data = { + "destination_path": "PATH/TO/OUTPUT/FILE", + "project": { + "name": "Testing project" + }, + "intent": "WIP", + "version_name": "seq01_sh0100_compositing_v01", + "date": "2019-08-09", + "shot_type": "2d comp", + "submission_note": ( + "Lorem ipsum dolor sit amet, consectetuer adipiscing elit." + " Aenean commodo ligula eget dolor. Aenean massa." + " Cum sociis natoque penatibus et magnis dis parturient montes," + " nascetur ridiculus mus. Donec quam felis, ultricies nec," + " pellentesque eu, pretium quis, sem. Nulla consequat massa quis" + " enim. Donec pede justo, fringilla vel," + " aliquet nec, vulputate eget, arcu." + ), + "thumbnail_path": "PATH/TO/THUMBNAIL/FILE", + "color_bar_path": "PATH/TO/COLOR/BAR/FILE", + "vendor": "Our Studio", + "shot_name": "sh0100", + "frame_start": 1001, + "frame_end": 1004, + "duration": 3 + } + + example_presets = {"example_HD": { + "width": 1920, + "height": 1080, + "destination_path": "{destination_path}", + "style": { + "*": { + "font-family": "arial", + "font-color": "#ffffff", + "font-bold": False, + "font-italic": False, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top" + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": True + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": False, + "font-italic": False, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": False, + "ellide": True, + "max-lines": 1 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "font-bold": True, + "ellide": False, + "word-wrap": True, + "max-lines": None + }, + "table-item-col[1]": { + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" + } + }, + "items": [{ + "type": "layer", + "direction": 1, + "name": "MainLayer", + "style": { + "#MainLayer": { + "width": 1094, + "height": 1000, + "margin": 25, + "padding": 0 + }, + "#LeftSide": { + "margin-right": 25 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "items": [{ + "type": "layer", + "direction": 1, + "style": { + "table-item": { + "bg-color": "transparent", + "padding-bottom": 20 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": True, + "font-size": 40 + } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "{project[name]}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[0:1]": { + "width": 580 + } + } + }, { + "type": "table", + "values": [ + ["Submitting For:", "{intent}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[0:1]": { + "width": 218, + "alignment-horizontal": "right" + } + } + }] + }, { + "type": "rectangle", + "style": { + "bg-color": "#bc1015", + "width": 1108, + "height": 5, + "fill": True + } + }, { + "type": "table", + "use_alternate_color": True, + "values": [ + ["Version name:", "{version_name}"], + ["Date:", "{date}"], + ["Shot Types:", "{shot_type}"], + ["Submission Note:", "{submission_note}"] + ], + "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[0:1]": { + "font-bold": True + }, + "table-item-field[3:0]": { + "word-wrap": True, + "ellide": True, + "max-lines": 4 + }, + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 150 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 958 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "items": [{ + "type": "placeholder", + "name": "thumbnail", + "path": "{thumbnail_path}", + "style": { + "width": 730, + "height": 412 + } + }, { + "type": "placeholder", + "name": "colorbar", + "path": "{color_bar_path}", + "return_data": True, + "style": { + "width": 730, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": True, + "values": [ + ["Vendor:", "{vendor}"], + ["Shot Name:", "{shot_name}"], + ["Frames:", "{frame_start} - {frame_end} ({duration})"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left", + "width": 200 + }, + "table-item-col[1]": { + "alignment-horizontal": "right", + "width": 530, + "font-size": 30 + } + } + }] + }] + }] + }} + + api.create_slates(example_fill_data, "example_HD", example_presets) diff --git a/pype/scripts/slates/slate_base/items.py b/pype/scripts/slates/slate_base/items.py index ea31443f80..1183d73305 100644 --- a/pype/scripts/slates/slate_base/items.py +++ b/pype/scripts/slates/slate_base/items.py @@ -1,3 +1,4 @@ +import os import re from PIL import Image diff --git a/pype/scripts/slates/lib.py b/pype/scripts/slates/slate_base/lib.py similarity index 63% rename from pype/scripts/slates/lib.py rename to pype/scripts/slates/slate_base/lib.py index 154c689349..3c7a465e98 100644 --- a/pype/scripts/slates/lib.py +++ b/pype/scripts/slates/slate_base/lib.py @@ -1,17 +1,19 @@ import logging - try: from queue import Queue except Exception: from Queue import Queue -from .slate_base.main_frame import MainFrame -from .slate_base.layer import Layer -from .slate_base.items import ( +from .main_frame import MainFrame +from .layer import Layer +from .items import ( ItemTable, ItemImage, ItemRectangle, ItemPlaceHolder ) -from pypeapp import config +try: + from pypeapp.config import get_presets +except Exception: + get_presets = dict log = logging.getLogger(__name__) @@ -19,17 +21,20 @@ log = logging.getLogger(__name__) RequiredSlateKeys = ["width", "height", "destination_path"] -def create_slates(fill_data, slate_name): - presets = config.get_presets() - slate_presets = ( - presets - .get("tools", {}) - .get("slates") - ) or {} +def create_slates(fill_data, slate_name, slate_presets=None): + if slate_presets is None: + presets = get_presets() + slate_presets = ( + presets + .get("tools", {}) + .get("slates") + ) or {} slate_data = slate_presets.get(slate_name) if not slate_data: - log.error("Slate data of <{}> does not exists.") + log.error( + "Name \"{}\" was not found in slate presets.".format(slate_name) + ) return False missing_keys = [] @@ -111,41 +116,3 @@ def create_slates(fill_data, slate_name): main.draw() log.debug("Slate creation finished") - - -def example(): - # import sys - # sys.append(r"PATH/TO/PILLOW/PACKAGE") - # sys.append(r"PATH/TO/PYPE-SETUP") - - fill_data = { - "destination_path": "PATH/TO/OUTPUT/FILE", - "project": { - "name": "Testing project" - }, - "intent": "WIP", - "version_name": "seq01_sh0100_compositing_v01", - "date": "2019-08-09", - "shot_type": "2d comp", - "submission_note": ( - "Lorem ipsum dolor sit amet, consectetuer adipiscing elit." - " Aenean commodo ligula eget dolor. Aenean massa." - " Cum sociis natoque penatibus et magnis dis parturient montes," - " nascetur ridiculus mus. Donec quam felis, ultricies nec," - " pellentesque eu, pretium quis, sem. Nulla consequat massa quis" - " enim. Donec pede justo, fringilla vel," - " aliquet nec, vulputate eget, arcu." - ), - "thumbnail_path": "PATH/TO/THUMBNAIL/FILE", - "color_bar_path": "PATH/TO/COLOR/BAR/FILE", - "vendor": "Our Studio", - "shot_name": "sh0100", - "frame_start": 1001, - "frame_end": 1004, - "duration": 3 - } - create_slates(fill_data, "example_HD") - - -if __name__ == "__main__": - example() From a5d313492f7f157513c124894289f297a7ec237c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Mar 2020 16:27:52 +0100 Subject: [PATCH 40/42] hopefully fix creating items --- pype/scripts/slates/slate_base/items.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pype/scripts/slates/slate_base/items.py b/pype/scripts/slates/slate_base/items.py index 1183d73305..6d19fc6a0c 100644 --- a/pype/scripts/slates/slate_base/items.py +++ b/pype/scripts/slates/slate_base/items.py @@ -36,8 +36,8 @@ class ItemImage(BaseItem): obj_type = "image" def __init__(self, image_path, *args, **kwargs): - super(ItemImage, self).__init__(*args, **kwargs) self.image_path = image_path + super(ItemImage, self).__init__(*args, **kwargs) def fill_data_format(self): if re.match(self.fill_data_regex, self.image_path): @@ -143,8 +143,8 @@ class ItemText(BaseItem): obj_type = "text" def __init__(self, value, *args, **kwargs): - super(ItemText, self).__init__(*args, **kwargs) self.value = value + super(ItemText, self).__init__(*args, **kwargs) def draw(self, image, drawer): bg_color = self.style["bg-color"] From 3e216dc3b39026515aa8ff2d86b25224ce68bb8f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Mar 2020 12:04:00 +0100 Subject: [PATCH 41/42] slates can receive data though command line like burnins and it is possible to save slates metadata to json --- pype/scripts/slates/__main__.py | 13 +++++-- pype/scripts/slates/slate_base/lib.py | 56 +++++++++++++++++++++------ 2 files changed, 55 insertions(+), 14 deletions(-) diff --git a/pype/scripts/slates/__main__.py b/pype/scripts/slates/__main__.py index 29282d3226..43efcfbb06 100644 --- a/pype/scripts/slates/__main__.py +++ b/pype/scripts/slates/__main__.py @@ -1,10 +1,17 @@ +import json from slate_base import api def main(in_args=None): - # TODO proper argument handling - api.example() + data_arg = in_args[-1] + in_data = json.loads(data_arg) + api.create_slates( + in_data["fill_data"], + in_data.get("slate_name"), + in_data.get("slate_data"), + in_data.get("data_output_json") + ) if __name__ == "__main__": - main() + main(sys.argv) diff --git a/pype/scripts/slates/slate_base/lib.py b/pype/scripts/slates/slate_base/lib.py index 3c7a465e98..d9f8ad6d42 100644 --- a/pype/scripts/slates/slate_base/lib.py +++ b/pype/scripts/slates/slate_base/lib.py @@ -1,3 +1,5 @@ +import os +import json import logging try: from queue import Queue @@ -21,21 +23,36 @@ log = logging.getLogger(__name__) RequiredSlateKeys = ["width", "height", "destination_path"] -def create_slates(fill_data, slate_name, slate_presets=None): - if slate_presets is None: - presets = get_presets() +# TODO proper documentation +def create_slates( + fill_data, slate_name=None, slate_data=None, data_output_json=None +): + """Implmentation for command line executing. + + Data for slates are by defaule taken from presets. That requires to enter, + `slate_name`. If `slate_data` are entered then they are used. + + `data_output` should be path to json file where data will be collected. + """ + if slate_data is None and slate_name is None: + raise TypeError( + "`create_slates` expects to enter data for slates or name" + " of slate preset." + ) + + elif slate_data is None: slate_presets = ( - presets + get_presets() .get("tools", {}) .get("slates") ) or {} - slate_data = slate_presets.get(slate_name) - - if not slate_data: - log.error( - "Name \"{}\" was not found in slate presets.".format(slate_name) - ) - return False + slate_data = slate_presets.get(slate_name) + if slate_data is None: + raise ValueError( + "Preset name \"{}\" was not found in slate presets.".format( + slate_name + ) + ) missing_keys = [] for key in RequiredSlateKeys: @@ -116,3 +133,20 @@ def create_slates(fill_data, slate_name, slate_presets=None): main.draw() log.debug("Slate creation finished") + + if not data_output_json: + return + + if not data_output_json.endswith(".json"): + raise ValueError("Output path must be .json file.") + + data_output_json_dir = os.path.dirname(data_output_json) + if not os.path.exists(data_output_json_dir): + log.info("Creating folder \"{}\"".format(data_output_json_dir)) + os.makedirs(data_output_json_dir) + + output_data = main.collect_data() + with open(data_output_json, "w") as json_file: + json_file.write(json.dumps(output_data, indent=4)) + + log.info("Metadata collected in \"{}\".".format(data_output_json)) From 9a749a03a950afbcccb8467f6308f0d7456de4ad Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Mar 2020 12:06:04 +0100 Subject: [PATCH 42/42] added important import --- pype/scripts/slates/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pype/scripts/slates/__main__.py b/pype/scripts/slates/__main__.py index 43efcfbb06..bd49389d84 100644 --- a/pype/scripts/slates/__main__.py +++ b/pype/scripts/slates/__main__.py @@ -1,3 +1,4 @@ +import sys import json from slate_base import api