Skip to content

Commit

Permalink
Implement slate_fill_mode for SlateHtmlProcessor.
Browse files Browse the repository at this point in the history
  • Loading branch information
robin-ynput committed Oct 16, 2024
1 parent 6e0563b commit 932e690
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 51 deletions.
3 changes: 2 additions & 1 deletion lablib/generators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"""Generator module to be used by Processors and Renderers."""

from .ocio_config import OCIOConfigFileGenerator
from .slate_html import SlateHtmlGenerator
from .slate_html import SlateHtmlGenerator, SlateFillMode

__all__ = [
"OCIOConfigFileGenerator",
"SlateHtmlGenerator",
"SlateFillMode"
]
100 changes: 63 additions & 37 deletions lablib/generators/slate_html.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import annotations

import collections
import datetime
import os
from enum import Enum
import shutil
from typing import List, Dict
from pathlib import Path
Expand All @@ -13,21 +14,31 @@
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:
slate_template_path: The path to the template.
data: A dictionary containing the data to be formatted in the template.
width: The width of the slate.
height: The height of the slate.
staging_dir: The directory where the slate will be staged.
source_files: A list of source files.
is_source_linear: A boolean to set whether the source files are linear.
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,
Expand All @@ -36,14 +47,16 @@ def __init__(
height: int = None,
staging_dir: str = None,
source_files: List = None,
is_source_linear: bool = 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
Expand Down Expand Up @@ -156,6 +169,35 @@ def _stage_slate(self) -> str:
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.
Expand All @@ -179,13 +221,7 @@ def _format_slate(self) -> None:
with open(self._slate_staged_path, "r+") as f:
template_content = f.read()

# Attempt to replace/format template with content.
try:
content = template_content.format(**formatted_dict)
except KeyError as error:
raise ValueError(
f"Key mismatch, cannot fill template: {error}"
) from error
content = self.__format_template(template_content, formatted_dict)

# Override template file content with formatted data.
with open(self._slate_staged_path, "w+") as f:
Expand All @@ -195,27 +231,17 @@ def _format_slate(self) -> None:

self._driver.get(self._slate_staged_path)

# TODO: Revisit this.
# Currently this function will fail with a KeyError
# if any data expected by the template is missing from
# the data dict.
#
# The code below turns this into a silent fails where
# missig fields are hidden from the resulting slate.
# elements = self._driver.find_elements(
# By.XPATH,
# "//*[contains(text(),'{}')]".format("**MISSING"),
# )
# for el in elements:
# self._driver.execute_script(
# "var element = arguments[0];\n" "element.style.display = 'none';", el
# )
# if self._remove_missing_parents:
# parent = el.find_element(By.XPATH, "..")
# self._driver.execute_script(
# "var element = arguments[0];\n" "element.style.display = 'none';",
# parent,
# )
# 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)

Expand Down
6 changes: 3 additions & 3 deletions templates/slates/slate_generic/slate_generic.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<!--<img class="logo_footer" src="logo.svg" />-->
<div class="main-container">
<div class="info-container">
<div class="title">{project[name]}</div>
<div class="subtitle">{asset}_{task[short]}_v{@version}</div>
<div class="title">{project_name}</div>
<div class="subtitle">{asset}_{task_short}_v{@version}</div>
<div class="info-entry">
<div class="info-desc">Date:</div>
<div class="info-text">{dd} {mmm} {yyyy}</div>
Expand All @@ -26,7 +26,7 @@
</div>
<div class="info-entry">
<div class="info-desc">Intent:</div>
<div class="info-text">{intent[value]}</div>
<div class="info-text">{intent}</div>
</div>
<div class="info-entry">
<div class="info-desc">Notes:</div>
Expand Down
53 changes: 43 additions & 10 deletions tests/test_renderers_slate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest

from lablib.lib import SequenceInfo
from lablib.generators import SlateHtmlGenerator
from lablib.generators import SlateHtmlGenerator, SlateFillMode
from lablib.renderers import SlateRenderer


Expand Down Expand Up @@ -56,6 +56,7 @@ def source_dir():
# Remove all temporary directory content.
shutil.rmtree(temp_dir)


def test_Slaterenderer_missing_keys():
""" An Exception should raise if any key defined in the
template but missing in the provided data.
Expand All @@ -68,15 +69,47 @@ def test_Slaterenderer_missing_keys():
_run_slate_renderer(generator)


def test_Slaterenderer_unknown_mode():
""" An Exception should raise if the provided slate mode is unknown.
"""
with pytest.raises(ValueError):
generator = SlateHtmlGenerator(
{"missing": "data"},
SLATE_TEMPLATE_FILE,
slate_fill_mode="UNKNOWN MODE"
)
_run_slate_renderer(generator)


def test_Slaterenderer_missing_keys_hide(source_dir):
""" Slate should go through even with missing data
when using HIDE_WHEN_MISSING mode.
"""
source_sequence = SequenceInfo.scan(source_dir)[0]
generator = SlateHtmlGenerator(
{"missing": "data"},
SLATE_TEMPLATE_FILE,
slate_fill_mode=SlateFillMode.HIDE_FIELD_WHEN_MISSING
)
_run_slate_renderer(generator, sequence=source_sequence)

edited_sequence = SequenceInfo.scan(source_dir)[0]
slate_frame = edited_sequence.frames[0]

assert len(edited_sequence.frames) == len(source_sequence.frames) + 1
assert slate_frame.width == 1920
assert slate_frame.height == 1080


def test_Slaterenderer_default(source_dir):
""" Ensure a valid HD slate is generated from default.
"""
source_sequence = SequenceInfo.scan(source_dir)[0]
generator = SlateHtmlGenerator(
{
"project": {"name": "test_project"},
"intent": {"value": "test_intent"},
"task": {"short": "test_task"},
"project_name": "test_project",
"intent": "test_intent",
"task_short": "test_task",
"asset": "test_asset",
"comment": "some random comment",
"scope": "test_scope",
Expand All @@ -100,9 +133,9 @@ def test_Slaterenderer_4K(source_dir):
source_sequence = SequenceInfo.scan(source_dir)[0]
generator = SlateHtmlGenerator(
{
"project": {"name": "test_project"},
"intent": {"value": "test_intent"},
"task": {"short": "test_task"},
"project_name": "test_project",
"intent": "test_intent",
"task_short": "test_task",
"asset": "test_asset",
"comment": "some random comment",
"scope": "test_scope",
Expand All @@ -128,9 +161,9 @@ def test_Slaterenderer_explicit_output(source_dir):
expected_output = pathlib.Path(source_dir) / "output.exr"
generator = SlateHtmlGenerator(
{
"project": {"name": "test_project"},
"intent": {"value": "test_intent"},
"task": {"short": "test_task"},
"project_name": "test_project",
"intent": "test_intent",
"task_short": "test_task",
"asset": "test_asset",
"comment": "some random comment",
"scope": "test_scope",
Expand Down

0 comments on commit 932e690

Please sign in to comment.