Merge branch 'develop' into feature/1235-hiero-unify-otio-workflow-from-resolve

This commit is contained in:
Jakub Jezek 2021-04-27 16:37:26 +02:00
commit 36e76837c5
No known key found for this signature in database
GPG key ID: D8548FBF690B100A
13 changed files with 1228 additions and 504 deletions

View file

@ -1,4 +1,5 @@
import os
import re
import subprocess
from openpype.lib import PreLaunchHook
@ -31,10 +32,46 @@ class InstallPySideToBlender(PreLaunchHook):
def inner_execute(self):
# Get blender's python directory
version_regex = re.compile(r"^2\.[0-9]{2}$")
executable = self.launch_context.executable.executable_path
# Blender installation contain subfolder named with it's version where
# python binaries are stored.
version_subfolder = self.launch_context.app_name.split("_")[1]
if os.path.basename(executable).lower() != "blender.exe":
self.log.info((
"Executable does not lead to blender.exe file. Can't determine"
" blender's python to check/install PySide2."
))
return
executable_dir = os.path.dirname(executable)
version_subfolders = []
for name in os.listdir(executable_dir):
fullpath = os.path.join(name, executable_dir)
if not os.path.isdir(fullpath):
continue
if not version_regex.match(name):
continue
version_subfolders.append(name)
if not version_subfolders:
self.log.info(
"Didn't find version subfolder next to Blender executable"
)
return
if len(version_subfolders) > 1:
self.log.info((
"Found more than one version subfolder next"
" to blender executable. {}"
).format(", ".join([
'"./{}"'.format(name)
for name in version_subfolders
])))
return
version_subfolder = version_subfolders[0]
pythond_dir = os.path.join(
os.path.dirname(executable),
version_subfolder,
@ -65,6 +102,7 @@ class InstallPySideToBlender(PreLaunchHook):
# Check if PySide2 is installed and skip if yes
if self.is_pyside_installed(python_executable):
self.log.debug("Blender has already installed PySide2.")
return
# Install PySide2 in blender's python

View file

@ -0,0 +1,162 @@
import os
from avalon import api, pipeline
from avalon.unreal import lib
from avalon.unreal import pipeline as unreal_pipeline
import unreal
class PointCacheAlembicLoader(api.Loader):
"""Load Point Cache from Alembic"""
families = ["model", "pointcache"]
label = "Import Alembic Point Cache"
representations = ["abc"]
icon = "cube"
color = "orange"
def load(self, context, name, namespace, data):
"""
Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and avalon container
root = "/Game/Avalon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}/{}".format(root, asset, name), suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = unreal.AssetImportTask()
task.set_editor_property('filename', self.fname)
task.set_editor_property('destination_path', asset_dir)
task.set_editor_property('destination_name', asset_name)
task.set_editor_property('replace_existing', False)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options = unreal.AbcImportSettings()
options.set_editor_property(
'import_type', unreal.AlembicImportType.GEOMETRY_CACHE)
options.geometry_cache_settings.set_editor_property(
'flatten_tracks', False)
task.options = options
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
# Create Asset Container
lib.create_avalon_container(
container=container_name, path=asset_dir)
data = {
"schema": "openpype:container-2.0",
"id": pipeline.AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = api.get_representation_path(representation)
destination_path = container["namespace"]
task = unreal.AssetImportTask()
task.set_editor_property('filename', source_path)
task.set_editor_property('destination_path', destination_path)
# strip suffix
task.set_editor_property('destination_name', name)
task.set_editor_property('replace_existing', True)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options = unreal.AbcImportSettings()
options.set_editor_property(
'import_type', unreal.AlembicImportType.GEOMETRY_CACHE)
options.geometry_cache_settings.set_editor_property(
'flatten_tracks', False)
task.options = options
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
def remove(self, container):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
parent_path, recursive=False
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)

View file

@ -0,0 +1,156 @@
import os
from avalon import api, pipeline
from avalon.unreal import lib
from avalon.unreal import pipeline as unreal_pipeline
import unreal
class SkeletalMeshAlembicLoader(api.Loader):
"""Load Unreal SkeletalMesh from Alembic"""
families = ["pointcache"]
label = "Import Alembic Skeletal Mesh"
representations = ["abc"]
icon = "cube"
color = "orange"
def load(self, context, name, namespace, data):
"""
Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and avalon container
root = "/Game/Avalon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}/{}".format(root, asset, name), suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = unreal.AssetImportTask()
task.set_editor_property('filename', self.fname)
task.set_editor_property('destination_path', asset_dir)
task.set_editor_property('destination_name', asset_name)
task.set_editor_property('replace_existing', False)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options = unreal.AbcImportSettings()
options.set_editor_property(
'import_type', unreal.AlembicImportType.SKELETAL)
task.options = options
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
# Create Asset Container
lib.create_avalon_container(
container=container_name, path=asset_dir)
data = {
"schema": "openpype:container-2.0",
"id": pipeline.AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = api.get_representation_path(representation)
destination_path = container["namespace"]
task = unreal.AssetImportTask()
task.set_editor_property('filename', source_path)
task.set_editor_property('destination_path', destination_path)
# strip suffix
task.set_editor_property('destination_name', name)
task.set_editor_property('replace_existing', True)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options = unreal.AbcImportSettings()
options.set_editor_property(
'import_type', unreal.AlembicImportType.SKELETAL)
task.options = options
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
def remove(self, container):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
parent_path, recursive=False
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)

View file

@ -0,0 +1,156 @@
import os
from avalon import api, pipeline
from avalon.unreal import lib
from avalon.unreal import pipeline as unreal_pipeline
import unreal
class StaticMeshAlembicLoader(api.Loader):
"""Load Unreal StaticMesh from Alembic"""
families = ["model"]
label = "Import Alembic Static Mesh"
representations = ["abc"]
icon = "cube"
color = "orange"
def load(self, context, name, namespace, data):
"""
Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
Returns:
list(str): list of container content
"""
# Create directory for asset and avalon container
root = "/Game/Avalon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}/{}".format(root, asset, name), suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = unreal.AssetImportTask()
task.set_editor_property('filename', self.fname)
task.set_editor_property('destination_path', asset_dir)
task.set_editor_property('destination_name', asset_name)
task.set_editor_property('replace_existing', False)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options = unreal.AbcImportSettings()
options.set_editor_property(
'import_type', unreal.AlembicImportType.STATIC_MESH)
task.options = options
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
# Create Asset Container
lib.create_avalon_container(
container=container_name, path=asset_dir)
data = {
"schema": "openpype:container-2.0",
"id": pipeline.AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = api.get_representation_path(representation)
destination_path = container["namespace"]
task = unreal.AssetImportTask()
task.set_editor_property('filename', source_path)
task.set_editor_property('destination_path', destination_path)
# strip suffix
task.set_editor_property('destination_name', name)
task.set_editor_property('replace_existing', True)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options = unreal.AbcImportSettings()
options.set_editor_property(
'import_type', unreal.AlembicImportType.STATIC_MESH)
task.options = options
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
)
for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)
def remove(self, container):
path = container["namespace"]
parent_path = os.path.dirname(path)
unreal.EditorAssetLibrary.delete_directory(path)
asset_content = unreal.EditorAssetLibrary.list_assets(
parent_path, recursive=False
)
if len(asset_content) == 0:
unreal.EditorAssetLibrary.delete_directory(parent_path)

View file

@ -1,7 +1,6 @@
import os
from avalon import api, pipeline
from avalon import unreal as avalon_unreal
from avalon.unreal import lib
from avalon.unreal import pipeline as unreal_pipeline
import unreal

View file

@ -7,7 +7,7 @@ from openpype import resources
from openpype.modules.sync_server.tray.widgets import (
SyncProjectListWidget,
SyncRepresentationWidget
SyncRepresentationSummaryWidget
)
log = PypeLogger().get_logger("SyncServer")
@ -47,7 +47,7 @@ class SyncServerWindow(QtWidgets.QDialog):
left_column_layout.addWidget(self.pause_btn)
left_column.setLayout(left_column_layout)
repres = SyncRepresentationWidget(
repres = SyncRepresentationSummaryWidget(
sync_server,
project=self.projects.current_project,
parent=self)

View file

@ -24,6 +24,7 @@ ProgressRole = QtCore.Qt.UserRole + 4
DateRole = QtCore.Qt.UserRole + 6
FailedRole = QtCore.Qt.UserRole + 8
HeaderNameRole = QtCore.Qt.UserRole + 10
FullItemRole = QtCore.Qt.UserRole + 12
@six.add_metaclass(abc.ABCMeta)
@ -128,6 +129,7 @@ class FilterDefinition:
type = attr.ib()
values = attr.ib(factory=list)
def pretty_size(value, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(value) < 1024.0:
@ -156,3 +158,9 @@ def translate_provider_for_icon(sync_server, project, site):
if site == sync_server.DEFAULT_SITE:
return sync_server.DEFAULT_SITE
return sync_server.get_provider_for_site(project, site)
def get_item_by_id(model, object_id):
index = model.get_index(object_id)
item = model.data(index, FullItemRole)
return item

View file

@ -120,7 +120,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
self.query = self.get_query(load_records)
representations = self.dbcon.aggregate(self.query)
self.add_page_records(self.local_site, self.remote_site,
self.add_page_records(self.active_site, self.remote_site,
representations)
self.endResetModel()
self.refresh_finished.emit()
@ -158,7 +158,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
self._rec_loaded,
self._rec_loaded + items_to_fetch - 1)
self.add_page_records(self.local_site, self.remote_site,
self.add_page_records(self.active_site, self.remote_site,
representations)
self.endInsertRows()
@ -283,7 +283,7 @@ class _SyncRepresentationModel(QtCore.QAbstractTableModel):
"""
self._project = project
self.sync_server.set_sync_project_settings()
self.local_site = self.sync_server.get_active_site(self.project)
self.active_site = self.sync_server.get_active_site(self.project)
self.remote_site = self.sync_server.get_remote_site(self.project)
self.refresh()
@ -410,7 +410,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
self.sync_server = sync_server
# TODO think about admin mode
# this is for regular user, always only single local and single remote
self.local_site = self.sync_server.get_active_site(self.project)
self.active_site = self.sync_server.get_active_site(self.project)
self.remote_site = self.sync_server.get_remote_site(self.project)
self.sort = self.DEFAULT_SORT
@ -428,6 +428,9 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
def data(self, index, role):
item = self._data[index.row()]
if role == lib.FullItemRole:
return item
header_value = self._header[index.column()]
if role == lib.ProviderRole:
if header_value == 'local_site':
@ -585,7 +588,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
}},
'order_local': {
'$filter': {'input': '$files.sites', 'as': 'p',
'cond': {'$eq': ['$$p.name', self.local_site]}
'cond': {'$eq': ['$$p.name', self.active_site]}
}}
}},
{'$addFields': {
@ -714,7 +717,7 @@ class SyncRepresentationSummaryModel(_SyncRepresentationModel):
"""
base_match = {
"type": "representation",
'files.sites.name': {'$all': [self.local_site,
'files.sites.name': {'$all': [self.active_site,
self.remote_site]}
}
if not self._word_filter:
@ -889,7 +892,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
self.sync_server = sync_server
# TODO think about admin mode
# this is for regular user, always only single local and single remote
self.local_site = self.sync_server.get_active_site(self.project)
self.active_site = self.sync_server.get_active_site(self.project)
self.remote_site = self.sync_server.get_remote_site(self.project)
self.sort = self.DEFAULT_SORT
@ -905,6 +908,9 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
def data(self, index, role):
item = self._data[index.row()]
if role == lib.FullItemRole:
return item
header_value = self._header[index.column()]
if role == lib.ProviderRole:
if header_value == 'local_site':
@ -1042,7 +1048,7 @@ class SyncRepresentationDetailModel(_SyncRepresentationModel):
}},
'order_local': {
'$filter': {'input': '$files.sites', 'as': 'p',
'cond': {'$eq': ['$$p.name', self.local_site]}
'cond': {'$eq': ['$$p.name', self.active_site]}
}}
}},
{'$addFields': {

File diff suppressed because it is too large Load diff

View file

@ -704,6 +704,105 @@ class ExtractReview(pyblish.api.InstancePlugin):
return audio_in_args, audio_filters, audio_out_args
def get_letterbox_filters(
self,
letter_box_def,
input_res_ratio,
output_res_ratio,
pixel_aspect,
scale_factor_by_width,
scale_factor_by_height
):
output = []
ratio = letter_box_def["ratio"]
state = letter_box_def["state"]
fill_color = letter_box_def["fill_color"]
f_red, f_green, f_blue, f_alpha = fill_color
fill_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format(
f_red, f_green, f_blue
)
fill_color_alpha = float(f_alpha) / 255
line_thickness = letter_box_def["line_thickness"]
line_color = letter_box_def["line_color"]
l_red, l_green, l_blue, l_alpha = line_color
line_color_hex = "{0:0>2X}{1:0>2X}{2:0>2X}".format(
l_red, l_green, l_blue
)
line_color_alpha = float(l_alpha) / 255
if input_res_ratio == output_res_ratio:
ratio /= pixel_aspect
elif input_res_ratio < output_res_ratio:
ratio /= scale_factor_by_width
else:
ratio /= scale_factor_by_height
if state == "letterbox":
if fill_color_alpha > 0:
top_box = (
"drawbox=0:0:iw:round((ih-(iw*(1/{})))/2):t=fill:c={}@{}"
).format(ratio, fill_color_hex, fill_color_alpha)
bottom_box = (
"drawbox=0:ih-round((ih-(iw*(1/{0})))/2)"
":iw:round((ih-(iw*(1/{0})))/2):t=fill:c={1}@{2}"
).format(ratio, fill_color_hex, fill_color_alpha)
output.extend([top_box, bottom_box])
if line_color_alpha > 0 and line_thickness > 0:
top_line = (
"drawbox=0:round((ih-(iw*(1/{0})))/2)-{1}:iw:{1}:"
"t=fill:c={2}@{3}"
).format(
ratio, line_thickness, line_color_hex, line_color_alpha
)
bottom_line = (
"drawbox=0:ih-round((ih-(iw*(1/{})))/2)"
":iw:{}:t=fill:c={}@{}"
).format(
ratio, line_thickness, line_color_hex, line_color_alpha
)
output.extend([top_line, bottom_line])
elif state == "pillar":
if fill_color_alpha > 0:
left_box = (
"drawbox=0:0:round((iw-(ih*{}))/2):ih:t=fill:c={}@{}"
).format(ratio, fill_color_hex, fill_color_alpha)
right_box = (
"drawbox=iw-round((iw-(ih*{0}))/2))"
":0:round((iw-(ih*{0}))/2):ih:t=fill:c={1}@{2}"
).format(ratio, fill_color_hex, fill_color_alpha)
output.extend([left_box, right_box])
if line_color_alpha > 0 and line_thickness > 0:
left_line = (
"drawbox=round((iw-(ih*{}))/2):0:{}:ih:t=fill:c={}@{}"
).format(
ratio, line_thickness, line_color_hex, line_color_alpha
)
right_line = (
"drawbox=iw-round((iw-(ih*{}))/2))"
":0:{}:ih:t=fill:c={}@{}"
).format(
ratio, line_thickness, line_color_hex, line_color_alpha
)
output.extend([left_line, right_line])
else:
raise ValueError(
"Letterbox state \"{}\" is not recognized".format(state)
)
return output
def rescaling_filters(self, temp_data, output_def, new_repre):
"""Prepare vieo filters based on tags in new representation.
@ -715,7 +814,8 @@ class ExtractReview(pyblish.api.InstancePlugin):
"""
filters = []
letter_box = output_def.get("letter_box")
letter_box_def = output_def["letter_box"]
letter_box_enabled = letter_box_def["enabled"]
# Get instance data
pixel_aspect = temp_data["pixel_aspect"]
@ -795,7 +895,7 @@ class ExtractReview(pyblish.api.InstancePlugin):
if (
output_width == input_width
and output_height == input_height
and not letter_box
and not letter_box_enabled
and pixel_aspect == 1
):
self.log.debug(
@ -834,30 +934,24 @@ class ExtractReview(pyblish.api.InstancePlugin):
)
# letter_box
if letter_box:
if input_res_ratio == output_res_ratio:
letter_box /= pixel_aspect
elif input_res_ratio < output_res_ratio:
letter_box /= scale_factor_by_width
else:
letter_box /= scale_factor_by_height
scale_filter = "scale={}x{}:flags=lanczos".format(
output_width, output_height
if letter_box_enabled:
filters.extend([
"scale={}x{}:flags=lanczos".format(
output_width, output_height
),
"setsar=1"
])
filters.extend(
self.get_letterbox_filters(
letter_box_def,
input_res_ratio,
output_res_ratio,
pixel_aspect,
scale_factor_by_width,
scale_factor_by_height
)
)
top_box = (
"drawbox=0:0:iw:round((ih-(iw*(1/{})))/2):t=fill:c=black"
).format(letter_box)
bottom_box = (
"drawbox=0:ih-round((ih-(iw*(1/{0})))/2)"
":iw:round((ih-(iw*(1/{0})))/2):t=fill:c=black"
).format(letter_box)
# Add letter box filters
filters.extend([scale_filter, "setsar=1", top_box, bottom_box])
# scaling none square pixels and 1920 width
if (
input_height != output_height

View file

@ -45,7 +45,25 @@
]
},
"width": 0,
"height": 0
"height": 0,
"letter_box": {
"enabled": false,
"ratio": 0.0,
"state": "letterbox",
"fill_color": [
0,
0,
0,
255
],
"line_thickness": 0,
"line_color": [
255,
0,
0,
255
]
}
}
}
}

View file

@ -203,6 +203,69 @@
"default": 0,
"minimum": 0,
"maximum": 100000
},
{
"key": "letter_box",
"label": "Letter box",
"type": "dict",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled",
"default": false
},
{
"key": "ratio",
"label": "Ratio",
"type": "number",
"decimal": 4,
"default": 0,
"minimum": 0,
"maximum": 10000
},
{
"key": "state",
"label": "Type",
"type": "enum",
"enum_items": [
{
"letterbox": "Letterbox"
},
{
"pillar": "Pillar"
}
]
},
{
"type": "schema_template",
"name": "template_rgba_color",
"template_data": [
{
"label": "Fill Color",
"name": "fill_color"
}
]
},
{
"key": "line_thickness",
"label": "Line Thickness",
"type": "number",
"minimum": 0,
"maximum": 1000
},
{
"type": "schema_template",
"name": "template_rgba_color",
"template_data": [
{
"label": "Line Color",
"name": "line_color"
}
]
}
]
}
]
}

View file

@ -0,0 +1,33 @@
[
{
"type": "list-strict",
"key": "{name}",
"label": "{label}",
"object_types": [
{
"label": "R",
"type": "number",
"minimum": 0,
"maximum": 255
},
{
"label": "G",
"type": "number",
"minimum": 0,
"maximum": 255
},
{
"label": "B",
"type": "number",
"minimum": 0,
"maximum": 255
},
{
"label": "A",
"type": "number",
"minimum": 0,
"maximum": 255
}
]
}
]