diff --git a/lablib/__init__.py b/lablib/__init__.py index 9baed3e..2ac6d33 100644 --- a/lablib/__init__.py +++ b/lablib/__init__.py @@ -4,23 +4,14 @@ OIIORepositionProcessor, ) -from .generators import ( - OCIOConfigFileGenerator, - SlateHtmlGenerator, -) - -from .renderers import ( - SlateRenderer, - BasicRenderer, -) +from .generators import OCIOConfigFileGenerator +from .renderers import BasicRenderer __all__ = [ "OCIOConfigFileGenerator", "AYONHieroEffectsFileProcessor", "AYONOCIOLookFileProcessor", "OIIORepositionProcessor", - "SlateHtmlGenerator", - "SlateRenderer", "BasicRenderer", ] diff --git a/lablib/generators/__init__.py b/lablib/generators/__init__.py index 89500fc..efd755e 100644 --- a/lablib/generators/__init__.py +++ b/lablib/generators/__init__.py @@ -1,10 +1,7 @@ """Generator module to be used by Processors and Renderers.""" from .ocio_config import OCIOConfigFileGenerator -from .slate_html import SlateHtmlGenerator, SlateFillMode __all__ = [ - "OCIOConfigFileGenerator", - "SlateHtmlGenerator", - "SlateFillMode" + "OCIOConfigFileGenerator" ] diff --git a/lablib/generators/slate_html.py b/lablib/generators/slate_html.py deleted file mode 100644 index 8ab9cf6..0000000 --- a/lablib/generators/slate_html.py +++ /dev/null @@ -1,423 +0,0 @@ -from __future__ import annotations - -import collections -import datetime -from enum import Enum -import shutil -from pathlib import Path - -from selenium import webdriver -from selenium.webdriver.chrome.options import Options -from selenium.webdriver.common.by import By - -from ..lib import utils, imageio - - -class SlateFillMode(Enum): - """Slate fill mode (fill up template from data). - """ - RAISE_WHEN_MISSING = 1 # missing data from template : raise - HIDE_FIELD_WHEN_MISSING = 2 # missing data from template : hide field - - -class SlateHtmlGenerator: - """Class to generate a slate from a template. - - Attributes: - data (dict): A dictionary containing the data to be formatted in the template. - slate_template_path (str): The path to the template. - width (int): The width of the slate. - height (int): The height of the slate. - staging_dir (str): The directory where the slate will be staged. - source_files (list): A list of source files. - is_source_linear (bool): A boolean to set whether the source files are linear. - slate_fill_mode (SlateFillMode): The template fill mode. - - Raises: - ValueError: When the provided slate template path is invalid. - """ - _MISSING_FIELD = "**MISSING**" - - def __init__( - self, - data: dict, - slate_template_path: str, - width: int = None, - height: int = None, - staging_dir: str = None, - source_files: list[imageio.ImageInfo] = None, - is_source_linear: bool = None, - slate_fill_mode: SlateFillMode = None - ): - self.data = data - self.width = width or 1920 - self.height = height or 1080 - self._staging_dir = staging_dir or utils.get_staging_dir() - self.source_files = source_files or [] - self.is_source_linear = is_source_linear if is_source_linear is not None else True - self._slate_fill_mode = slate_fill_mode or SlateFillMode.RAISE_WHEN_MISSING - - try: - slate_template_path = slate_template_path - self._slate_template_path = Path(slate_template_path).resolve().as_posix() - - except Exception as error: - raise ValueError( - "Could not load slate template" - f" from {slate_template_path}" - ) from error - - self._thumbs = [] - self._charts = [] - self._thumb_class_name: str = "thumb" - self._chart_class_name: str = "chart" - self._template_staging_dirname: str = "slate_staging" - self._slate_staged_path: str = None - self._slate_computed: str = None - self._slate_base_image_path: str = None - self.remove_missing_parents: bool = True - self.slate_base_name = "slate_base" - self.slate_extension = "png" - - options = Options() - # THIS WILL NEED TO BE SWITCHED TO NEW MODE, BUT THERE ARE BUGS. - # WE SHOULD BE FINE FOR A COUPLE OF YEARS UNTIL DEPRECATION. - # --headless=new works only with 100% display size, - # if you use a different display scaling (for hidpi monitors) - # the resizing of the screenshot will not work. - options.add_argument("--headless") - options.add_argument("--hide-scrollbars") - options.add_argument("--show-capture=no") - options.add_argument("--log-level=OFF") - options.add_argument("--no-sandbox") - options.add_argument("--disable-dev-shm-usage") - options.add_argument("--disable-extensions") - options.add_argument("--disable-gpu") - options.add_argument("--force-device-scale-factor=1") - options.add_experimental_option("excludeSwitches", ["enable-logging"]) - self._driver = webdriver.Chrome(options=options) - - @property - def staging_dir(self) -> str: - """Return the path to the staging directory. - - Returns: - str: The path to the staging directory. - """ - return self._staging_dir - - @property - def slate_filename(self) -> str: - """Return the slate filename. - - Returns: - str: The slate filename. - """ - return f"{self.slate_base_name}.{self.slate_extension}" - - @property - def template_path(self): - """Return the slate template path. - - Returns: - str: The slate template path. - """ - return self._slate_template_path - - @template_path.setter - def template_path(self, path: str) -> None: - """Set the slate template path. - - Arguments: - path (str): The new path to the slate template path. - """ - self._slate_template_path = Path(path).resolve().as_posix() - - def set_size(self, width: int, height: int) -> None: - """Set the slate resolution. - - Args: - width (int): The width of the slate. - height (int): The height of the slate. - """ - self.width = width - self.height = height - - def _stage_slate(self) -> str: - """Prepare staging content for slate generation. - - Returns: - str: The path to the prepped staging directory. - - Raises: - ValueError: When the provided template path is invalid. - """ - slate_path = Path(self.template_path).resolve() - slate_dir = slate_path.parent - slate_name = slate_path.name - slate_staging_dir = Path( - self.staging_dir, self._template_staging_dirname - ).resolve() - slate_staged_path = Path(slate_staging_dir, slate_name).resolve() - - # Clear staging path directory - shutil.rmtree(slate_staging_dir.as_posix(), ignore_errors=True) - - # Copy over template root directory - shutil.copytree(src=slate_dir.as_posix(), dst=slate_staging_dir.as_posix()) - - self._slate_staged_path = slate_staged_path.as_posix() - return self._slate_staged_path - - def __format_template(self, template_content: str, values: dict) -> str: - """ - Args: - template_content (str): The template content to format. - values (dict): The values to use for formatting. - - Returns: - str: The formatted string. - - Raises: - ValueError: When some key is missing or the mode is unknown. - """ - # Attempt to replace/format template with content. - if self._slate_fill_mode == SlateFillMode.RAISE_WHEN_MISSING: - try: - return template_content.format(**values) - except KeyError as error: - raise ValueError( - f"Key mismatch, cannot fill template: {error}" - ) from error - - elif self._slate_fill_mode == SlateFillMode.HIDE_FIELD_WHEN_MISSING: - default_values = collections.defaultdict(lambda: self._MISSING_FIELD) - default_values.update(values) - return template_content.format_map(default_values) - - else: - raise ValueError(f"Unknown slate fill mode {self._slate_fill_mode}.") - - def _format_slate(self) -> None: - """Format template with generator data values. - - Raises: - ValueError: When the provided data is incomplete. - """ - now = datetime.datetime.now() - default_data = { - "dd": now.day, - "mmm": now.month, - "yyyy": now.year, - "frame_start": self.source_files[0].frame_number, - "frame_end": self.source_files[-1].frame_number, - "resolution_width": self.width, - "resolution_height": self.height, - } - formatted_dict = default_data.copy() - formatted_dict.update(self.data) # overides with provided data. - - # Read template content - with open(self._slate_staged_path, "r+") as f: - template_content = f.read() - - content = self.__format_template(template_content, formatted_dict) - - # Override template file content with formatted data. - with open(self._slate_staged_path, "w+") as f: - f.seek(0) - f.write(content) - f.truncate() - - self._driver.get(self._slate_staged_path) - - # HIDE_FIELD_WHEN_MISSING mode - # The code below hide detected missing fields from the resulting slate. - elements = self._driver.find_elements( - By.XPATH, - "//*[contains(text(),'{}')]".format(self._MISSING_FIELD), - ) - for el in elements: - self._driver.execute_script( - "var element = arguments[0];\n" "element.style.display = 'none';", el - ) - - with open(self._slate_staged_path, "w") as f: - f.write(self._driver.page_source) - - def _setup_base_slate(self) -> str: - """Setup the slate template in selenium. - - Returns: - str: The slate final destination path. - """ - self._driver.get(self._slate_staged_path) - - window_size = self._driver.execute_script( - "return [window.outerWidth - window.innerWidth + arguments[0]," - "window.outerHeight - window.innerHeight + arguments[1]];", - self.width, - self.height, - ) - self._driver.set_window_size(*window_size) - - thumbs = self._driver.find_elements(By.CLASS_NAME, self._thumb_class_name) - for thumb in thumbs: - src_path = thumb.get_attribute("src") - if not src_path: - continue - - aspect_ratio = self.width / self.height - thumb_height = int(thumb.size["width"] / aspect_ratio) - self._driver.execute_script( - "var element = arguments[0];" "element.style.height = '{}px'".format( - thumb_height - ), - thumb, - ) - - path = src_path.replace("file:///", "") - thumb_image = imageio.ImageInfo(path=path) - thumb_image.origin_x = thumb.location["x"] - thumb_image.origin_y = thumb.location["y"] - thumb_image.width = thumb.size["width"] - thumb_image.height = thumb_height - self._thumbs.append(thumb_image) - - for thumb in thumbs: - self._driver.execute_script( - "var element = arguments[0];" - "element.parentNode.removeChild(element);", - thumb, - ) - - charts = self._driver.find_elements(By.CLASS_NAME, self._chart_class_name) - for chart in charts: - src_path = chart.get_attribute("src") - if src_path: - path=src_path.replace("file:///", "") - chart_image = imageio.ImageInfo(path=path) - chart_image.origin_x = chart.location["x"] - chart_image.origin_y = chart.location["y"] - chart_image.width = chart.size["width"] - chart_image.height = chart.size["height"] - self._charts.append(chart_image) - - for chart in charts: - self._driver.execute_script( - "var element = arguments[0];" - "element.parentNode.removeChild(element);", - chart, - ) - - template_staged_path = Path(self._slate_staged_path).resolve().parent - slate_base_path = Path(template_staged_path, self.slate_filename).resolve() - self._driver.save_screenshot(slate_base_path.as_posix()) - self._driver.quit() - self._slate_base_image_path = slate_base_path - return slate_base_path - - def _set_thumbnail_sources(self) -> None: - """Set thumbnail sources before processing slate. - """ - thumb_steps = int(len(self.source_files) / (len(self._thumbs) + 1)) - for i, t in enumerate(self._thumbs): - src_file = self.source_files[thumb_steps * (i + 1)] - src_file_path = src_file.filepath - self._thumbs[i].path = ( - Path(src_file_path).resolve().as_posix() - ) - - def create_base_slate(self) -> None: - """Prepare and create base slate. - """ - self._stage_slate() - self._format_slate() - self._setup_base_slate() - self._set_thumbnail_sources() - - def get_oiiotool_cmd(self) -> list: - """ Get the oiiotool command to run for slate generation. - - Returns: - list: The oiiotool command to run. - """ - label = "base" - cmd = [ - "-i", - Path(self._slate_base_image_path).resolve().as_posix(), - "--colorconvert", - "sRGB", - "linear", - "--ch", - "R,G,B,A=1.0", - "--label", - "slate", - "--create", - "{}x{}".format(self.width, self.height), - "4", - "--ch", - "R,G,B,A=0.0", - "--label", - label, - ] - for thumb in self._thumbs: - cmd.extend(["-i", thumb.path]) - if not self.is_source_linear: - cmd.extend(["--colorconvert", "sRGB", "linear"]) - - cmd.extend( - [ - "--ch", - "R,G,B,A=1.0", - "--resample", - "{}x{}+{}+{}".format( - thumb.width, thumb.height, thumb.origin_x, thumb.origin_y - ), - label, - "--over", - "--label", - "imgs", - ] - ) - label = "imgs" - - for chart in self._charts: - cmd.extend( - [ - "-i", - chart.path, - "--colorconvert", - "sRGB", - "linear", - "--ch", - "R,G,B,A=1.0", - "--resample", - "{}x{}+{}+{}".format( - chart.width, chart.height, chart.origin_x, chart.origin_y - ), - "imgs", - "--over", - "--label", - "imgs", - ] - ) - - cmd.extend( - [ - "slate", - "--over", - "--label", - "complete_slate", - ] - ) - if not self.is_source_linear: - cmd.extend( - [ - "--colorconvert", - "linear", - "sRGB", - ] - ) - - return cmd diff --git a/lablib/renderers/__init__.py b/lablib/renderers/__init__.py index d88e738..36f77d6 100644 --- a/lablib/renderers/__init__.py +++ b/lablib/renderers/__init__.py @@ -1,13 +1,11 @@ """Renderer module combining Operators, Generators and Processors for final image or video file.""" from .basic import BasicRenderer, Burnin, Codec -from .slate_render import SlateRenderer from .base import RendererBase __all__ = [ "BasicRenderer", "Burnin", "Codec", - "SlateRenderer", "RendererBase", ] diff --git a/lablib/renderers/slate_render.py b/lablib/renderers/slate_render.py deleted file mode 100644 index 263a92d..0000000 --- a/lablib/renderers/slate_render.py +++ /dev/null @@ -1,141 +0,0 @@ -from __future__ import annotations - -import subprocess -import shutil - -from pathlib import Path - -from ..lib.utils import call_iinfo, call_cmd, offset_timecode -from ..generators import SlateHtmlGenerator -from ..lib import SequenceInfo -from .basic import BasicRenderer -from .base import RendererBase - - -class SlateRenderer(RendererBase): - """Class for rendering slates. - - .. admonition:: Example - - .. code-block:: python - - # render slate image to 1080p - slate_generator = SlateHtmlGenerator( - # data used to fill up the slate template - { - "project": {"name": "test_project"}, - "intent": {"value": "test_intent"}, - "task": {"short": "test_task"}, - "asset": "test_asset", - "comment": "some random comment", - "scope": "test_scope", - "@version": "123", - }, - "/templates/slates/slate_generic/slate_generic.html", - ) - rend = SlateRenderer( - slate_generator, - SequenceInfo.scan("resources/public/plateMain/v002")[0], - ) - rend.render(debug=True) - """ - - def __init__( - self, - slate_generator: SlateHtmlGenerator, - source_sequence: SequenceInfo, - destination: str = None # default prepend to the source sequence - ): - self._slate_proc = slate_generator - self._dest = None # default destination - self._forced_dest = destination # explicit destination - - self._source_sequence = None - self.source_sequence = source_sequence - - self._thumbs: list = None - self._command: list = [] - - @property - def slate_generator(self) -> SlateHtmlGenerator: - """ - Returns: - SlateHtmlGenerator: The generator associated to the renderer. - """ - return self._slate_proc - - @slate_generator.setter - def slate_generator(self, generator: SlateHtmlGenerator) -> None: - """ - Args: - generator (SlateHtmlGenerator): The new generator for the renderer. - """ - self._slate_proc = generator - self._slate_proc.source_files = self._source_sequence.imageinfos - - @property - def source_sequence(self) -> SequenceInfo: - """Return the source sequence. - - Returns: - SequenceInfo. The source sequence. - """ - return self._source_sequence - - @source_sequence.setter - def source_sequence(self, source_sequence: SequenceInfo) -> None: - """Set new source sequence. - """ - self._source_sequence = source_sequence - self._slate_proc.source_files = self._source_sequence.imageinfos - - first_frame = min(self._source_sequence.imageinfos) - frame_number = first_frame.frame_number - slate_frame = str(frame_number - 1).zfill(source_sequence.padding) - ext = first_frame.extension - head, _, __ = first_frame.filepath.rsplit(".", 3) - - self._dest = f"{head}.{slate_frame}{ext}" - - @property - def destination(self): - """ - Returns: - str: The renderer destination. - """ - return self._forced_dest or self._dest - - def render(self, debug=False) -> None: - """Render the slate sequence. - - Arguments: - debug (Optional[bool]): Whether to increase log verbosity. - """ - first_frame = min(self.source_sequence.imageinfos) - timecode = offset_timecode( - tc=first_frame.timecode, - frame_offset=-1, - fps=first_frame.fps, - ) - self._slate_proc.create_base_slate() - - cmd = ["oiiotool"] - cmd.extend(self._slate_proc.get_oiiotool_cmd()) - cmd.extend( - [ - "--ch", - "R,G,B", - "--attrib:type=timecode", - "smpte:TimeCode", - timecode, - ] - ) - if debug: - cmd.extend(["--debug", "-v"]) - - cmd.extend(["-o", self.destination]) - call_cmd(cmd) - - slate_base_image_path = Path(self._slate_proc._slate_base_image_path).resolve() - slate_base_image_path.unlink() - shutil.rmtree(slate_base_image_path.parent) diff --git a/pyproject.toml b/pyproject.toml index 1bbf85b..656c401 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ readme = "README.md" python = ">=3.9.1,<3.10" opentimelineio = "^0.16.0" opencolorio = "<=2.3.2" -selenium = "4.16.0" [tool.poetry.dev-dependencies] pytest = "^6.2.0" diff --git a/start.ps1 b/start.ps1 index 1159c61..9e48fb0 100644 --- a/start.ps1 +++ b/start.ps1 @@ -22,6 +22,7 @@ function Default-Func { Write-Host " install Install Poetry and update venv by lock file." Write-Host " set-env Set all env vars in .env file." Write-Host " get-dependencies Download and extract all dependencies into vendor folder." + Write-Host " test Run tests." Write-Host "" } diff --git a/templates/slates/slate_generic/slate_generic.css b/templates/slates/slate_generic/slate_generic.css deleted file mode 100644 index 764d681..0000000 --- a/templates/slates/slate_generic/slate_generic.css +++ /dev/null @@ -1,89 +0,0 @@ -* { - padding: 0; - margin: 0; -} -body { - background-color: rgb(35, 35, 35); - font-family: Arial, sans-serif; - font-size: 3vh; - line-height: 3vh; - overflow: hidden; - color: rgb(200, 200, 200) -} -.logo_footer { - position: absolute; - height: 9vh; - bottom: 0; - padding-left: 9vh; - padding-bottom: 9vh; -} -.main-container { - display: grid; - width: 100%; - height: 100%; - grid-template: - "info-container sidebar"; - grid-template-rows: 100%; - grid-template-columns: 75% 25%; -} -.info-container { - grid-area: info-container; - padding: 9vh; - z-index: 1; - line-height: 3vh; -} -.title { - font-size: 9vh; - line-height: 9vh; - padding-bottom: 4vh; -} -.subtitle { - font-size: 5vh; - line-height: 5vh; - padding-bottom: 5vh; -} -.info-entry { - display: table-row; - line-height: 3vh; -} -.info-desc { - display: table-cell; - padding-right: 1.5vh; - color: rgb(100, 100, 100); - line-height: 3vh; -} -.info-text { - display: table-cell; - padding-bottom: 1.5vh; - width: 100%; - line-height: 3vh; -} -.sidebar { - grid-area: sidebar; - background-color: rgba(0, 0, 0, 1.0); - padding: 2.5vh; - display: grid; - grid-template-columns: 100%; - grid-template-rows: auto; - grid-template-areas: - "thumbs" - "." - "charts"; - z-index: 1; -} -.charts { - grid-area: charts; - width: 100%; - align-self: end; -} -.chart { - width: 100%; -} -.thumbs { - grid-area: thumbs; - width: 100%; -} -.thumb { - width: 100%; - margin-bottom: 2.5vh; -} \ No newline at end of file diff --git a/templates/slates/slate_generic/slate_generic.html b/templates/slates/slate_generic/slate_generic.html deleted file mode 100644 index 5f85f0e..0000000 --- a/templates/slates/slate_generic/slate_generic.html +++ /dev/null @@ -1,47 +0,0 @@ - -
- - - - -