Merge branch 'origin/automatic1111-webui' into andyxr/automatic1111-webui
commit
2b54cfd3e5
|
|
@ -16,7 +16,7 @@ class DeforumTQDM:
|
||||||
from .parseq_adapter import ParseqAdapter
|
from .parseq_adapter import ParseqAdapter
|
||||||
deforum_total = 0
|
deforum_total = 0
|
||||||
# FIXME: get only amount of steps
|
# FIXME: get only amount of steps
|
||||||
parseq_adapter = ParseqAdapter(self._parseq_args, self._anim_args, self._video_args, None, mute=True)
|
parseq_adapter = ParseqAdapter(self._parseq_args, self._anim_args, self._video_args, None, None, mute=True)
|
||||||
keys = DeformAnimKeys(self._anim_args) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
|
keys = DeformAnimKeys(self._anim_args) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
|
||||||
|
|
||||||
start_frame = 0
|
start_frame = 0
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ def print_combined_table(args, anim_args, p, keys, frame_idx):
|
||||||
rows2 = []
|
rows2 = []
|
||||||
if anim_args.animation_mode not in ['Video Input', 'Interpolation']:
|
if anim_args.animation_mode not in ['Video Input', 'Interpolation']:
|
||||||
if anim_args.animation_mode == '2D':
|
if anim_args.animation_mode == '2D':
|
||||||
field_names2 = ["Angle", "Zoom"]
|
field_names2 = ["Angle", "Zoom", "Tr C X", "Tr C Y"]
|
||||||
else:
|
else:
|
||||||
field_names2 = []
|
field_names2 = []
|
||||||
field_names2 += ["Tr X", "Tr Y"]
|
field_names2 += ["Tr X", "Tr Y"]
|
||||||
|
|
@ -294,7 +294,9 @@ def print_combined_table(args, anim_args, p, keys, frame_idx):
|
||||||
table.add_column(field_name, justify="center")
|
table.add_column(field_name, justify="center")
|
||||||
|
|
||||||
if anim_args.animation_mode == '2D':
|
if anim_args.animation_mode == '2D':
|
||||||
rows2 += [f"{keys.angle_series[frame_idx]:.5g}", f"{keys.zoom_series[frame_idx]:.5g}"]
|
rows2 += [f"{keys.angle_series[frame_idx]:.5g}", f"{keys.zoom_series[frame_idx]:.5g}",
|
||||||
|
f"{keys.transform_center_x_series[frame_idx]:.5g}", f"{keys.transform_center_y_series[frame_idx]:.5g}"]
|
||||||
|
|
||||||
rows2 += [f"{keys.translation_x_series[frame_idx]:.5g}", f"{keys.translation_y_series[frame_idx]:.5g}"]
|
rows2 += [f"{keys.translation_x_series[frame_idx]:.5g}", f"{keys.translation_y_series[frame_idx]:.5g}"]
|
||||||
|
|
||||||
if anim_args.animation_mode == '3D':
|
if anim_args.animation_mode == '3D':
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ def hybrid_generation(args, anim_args, root):
|
||||||
if not anim_args.hybrid_use_init_image:
|
if not anim_args.hybrid_use_init_image:
|
||||||
# determine max frames from length of input frames
|
# determine max frames from length of input frames
|
||||||
anim_args.max_frames = len(inputfiles)
|
anim_args.max_frames = len(inputfiles)
|
||||||
|
if anim_args.max_frames < 1:
|
||||||
|
raise Exception(f"Error: No input frames found in {video_in_frame_path}! Please check your input video path and whether you've opted to extract input frames.")
|
||||||
print(f"Using {anim_args.max_frames} input frames from {video_in_frame_path}...")
|
print(f"Using {anim_args.max_frames} input frames from {video_in_frame_path}...")
|
||||||
|
|
||||||
# use first frame as init
|
# use first frame as init
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,15 @@ from operator import itemgetter
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
from .animation_key_frames import DeformAnimKeys, ControlNetKeys
|
from .animation_key_frames import DeformAnimKeys, ControlNetKeys, LooperAnimKeys
|
||||||
from .rich import console
|
from .rich import console
|
||||||
|
|
||||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
|
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
|
||||||
|
|
||||||
|
IGNORED_FIELDS = ['fi', 'use_looper', 'imagesToKeyframe', 'schedules']
|
||||||
|
|
||||||
class ParseqAdapter():
|
class ParseqAdapter():
|
||||||
def __init__(self, parseq_args, anim_args, video_args, controlnet_args, mute=False):
|
def __init__(self, parseq_args, anim_args, video_args, controlnet_args, loop_args, mute=False):
|
||||||
|
|
||||||
# Basic data extraction
|
# Basic data extraction
|
||||||
self.use_parseq = parseq_args.parseq_manifest and parseq_args.parseq_manifest.strip()
|
self.use_parseq = parseq_args.parseq_manifest and parseq_args.parseq_manifest.strip()
|
||||||
|
|
@ -26,6 +28,8 @@ class ParseqAdapter():
|
||||||
# Wrap the original schedules with Parseq decorators, so that Parseq values will override the original values IFF appropriate.
|
# Wrap the original schedules with Parseq decorators, so that Parseq values will override the original values IFF appropriate.
|
||||||
self.anim_keys = ParseqAnimKeysDecorator(self, DeformAnimKeys(anim_args))
|
self.anim_keys = ParseqAnimKeysDecorator(self, DeformAnimKeys(anim_args))
|
||||||
self.cn_keys = ParseqControlNetKeysDecorator(self, ControlNetKeys(anim_args, controlnet_args)) if controlnet_args else None
|
self.cn_keys = ParseqControlNetKeysDecorator(self, ControlNetKeys(anim_args, controlnet_args)) if controlnet_args else None
|
||||||
|
# -1 because seed seems to be unused in LooperAnimKeys
|
||||||
|
self.looper_keys = ParseqLooperKeysDecorator(self, LooperAnimKeys(loop_args, anim_args, -1)) if loop_args else None
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
if (self.use_parseq):
|
if (self.use_parseq):
|
||||||
|
|
@ -78,9 +82,11 @@ class ParseqAdapter():
|
||||||
table.add_column("Parseq", style="cyan")
|
table.add_column("Parseq", style="cyan")
|
||||||
table.add_column("Deforum", style="green")
|
table.add_column("Deforum", style="green")
|
||||||
|
|
||||||
table.add_row("Anim Fields", '\n'.join(self.anim_keys.managed_fields()), '\n'.join(self.anim_keys.unmanaged_fields()))
|
table.add_row("Animation", '\n'.join(self.anim_keys.managed_fields()), '\n'.join(self.anim_keys.unmanaged_fields()))
|
||||||
if self.cn_keys:
|
if self.cn_keys:
|
||||||
table.add_row("Cn Fields", '\n'.join(self.cn_keys.managed_fields()), '\n'.join(self.cn_keys.unmanaged_fields()))
|
table.add_row("ControlNet", '\n'.join(self.cn_keys.managed_fields()), '\n'.join(self.cn_keys.unmanaged_fields()))
|
||||||
|
if self.looper_keys:
|
||||||
|
table.add_row("Guided Images", '\n'.join(self.looper_keys.managed_fields()), '\n'.join(self.looper_keys.unmanaged_fields()))
|
||||||
table.add_row("Prompts", "✅" if self.manages_prompts() else "❌", "✅" if not self.manages_prompts() else "❌")
|
table.add_row("Prompts", "✅" if self.manages_prompts() else "❌", "✅" if not self.manages_prompts() else "❌")
|
||||||
table.add_row("Frames", str(len(self.rendered_frames)), str(self.required_frames) + (" ⚠️" if str(self.required_frames) != str(len(self.rendered_frames))+"" else ""))
|
table.add_row("Frames", str(len(self.rendered_frames)), str(self.required_frames) + (" ⚠️" if str(self.required_frames) != str(len(self.rendered_frames))+"" else ""))
|
||||||
table.add_row("FPS", str(self.config_output_fps), str(self.required_fps) + (" ⚠️" if str(self.required_fps) != str(self.config_output_fps) else ""))
|
table.add_row("FPS", str(self.config_output_fps), str(self.required_fps) + (" ⚠️" if str(self.required_fps) != str(self.config_output_fps) else ""))
|
||||||
|
|
@ -180,12 +186,12 @@ class ParseqAbstractDecorator():
|
||||||
|
|
||||||
def managed_fields(self):
|
def managed_fields(self):
|
||||||
all_parseq_fields = self.all_parseq_fields()
|
all_parseq_fields = self.all_parseq_fields()
|
||||||
deforum_fields = [self.strip_suffixes(property) for property, _ in vars(self.fallback_keys).items() if property not in ['fi'] and not property.startswith('_')]
|
deforum_fields = [self.strip_suffixes(property) for property, _ in vars(self.fallback_keys).items() if property not in IGNORED_FIELDS and not property.startswith('_')]
|
||||||
return [field for field in deforum_fields if field in all_parseq_fields]
|
return [field for field in deforum_fields if field in all_parseq_fields]
|
||||||
|
|
||||||
def unmanaged_fields(self):
|
def unmanaged_fields(self):
|
||||||
all_parseq_fields = self.all_parseq_fields()
|
all_parseq_fields = self.all_parseq_fields()
|
||||||
deforum_fields = [self.strip_suffixes(property) for property, _ in vars(self.fallback_keys).items() if property not in ['fi'] and not property.startswith('_')]
|
deforum_fields = [self.strip_suffixes(property) for property, _ in vars(self.fallback_keys).items() if property not in IGNORED_FIELDS and not property.startswith('_')]
|
||||||
return [field for field in deforum_fields if field not in all_parseq_fields]
|
return [field for field in deforum_fields if field not in all_parseq_fields]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -239,5 +245,19 @@ class ParseqAnimKeysDecorator(ParseqAbstractDecorator):
|
||||||
self.prompts = super().parseq_to_series('deforum_prompt') # formatted as "{positive} --neg {negative}"
|
self.prompts = super().parseq_to_series('deforum_prompt') # formatted as "{positive} --neg {negative}"
|
||||||
|
|
||||||
|
|
||||||
|
class ParseqLooperKeysDecorator(ParseqAbstractDecorator):
|
||||||
|
def __init__(self, adapter: ParseqAdapter, looper_keys):
|
||||||
|
super().__init__(adapter, looper_keys)
|
||||||
|
|
||||||
|
# The Deforum UI offers an "Image strength schedule" in the Guided Images section,
|
||||||
|
# which simply overrides the strength schedule if guided images is enabled.
|
||||||
|
# In Parseq, we just re-use the same strength schedule.
|
||||||
|
self.image_strength_schedule_series = super().parseq_to_series('strength')
|
||||||
|
|
||||||
|
# We explicitly state the mapping for all other guided images fields so we can strip the prefix
|
||||||
|
# that we use in Parseq.
|
||||||
|
self.blendFactorMax_series = super().parseq_to_series('guided_blendFactorMax')
|
||||||
|
self.blendFactorSlope_series = super().parseq_to_series('guided_blendFactorSlope')
|
||||||
|
self.tweening_frames_schedule_series = super().parseq_to_series('guided_tweening_frames')
|
||||||
|
self.color_correction_factor_series = super().parseq_to_series('guidedcolor_correction_factor')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,20 @@ from types import SimpleNamespace
|
||||||
DEFAULT_ARGS = SimpleNamespace(anim_args = SimpleNamespace(max_frames=2),
|
DEFAULT_ARGS = SimpleNamespace(anim_args = SimpleNamespace(max_frames=2),
|
||||||
video_args = SimpleNamespace(fps=30),
|
video_args = SimpleNamespace(fps=30),
|
||||||
args = SimpleNamespace(seed=-1),
|
args = SimpleNamespace(seed=-1),
|
||||||
loop_args = SimpleNamespace(),
|
controlnet_args = SimpleNamespace(),
|
||||||
controlnet_args = SimpleNamespace())
|
loop_args = SimpleNamespace())
|
||||||
|
|
||||||
|
|
||||||
def buildParseqAdapter(parseq_use_deltas, parseq_manifest, setup_args=DEFAULT_ARGS):
|
def buildParseqAdapter(parseq_use_deltas, parseq_manifest, setup_args=DEFAULT_ARGS):
|
||||||
return ParseqAdapter(SimpleNamespace(parseq_use_deltas=parseq_use_deltas, parseq_manifest=parseq_manifest),
|
return ParseqAdapter(SimpleNamespace(parseq_use_deltas=parseq_use_deltas, parseq_manifest=parseq_manifest),
|
||||||
setup_args.anim_args, setup_args.video_args, setup_args.controlnet_args)
|
setup_args.anim_args, setup_args.video_args, setup_args.controlnet_args, setup_args.loop_args)
|
||||||
|
|
||||||
class TestParseqAnimKeys(unittest.TestCase):
|
class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_withprompt(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_withprompt(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -47,7 +48,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_withoutprompt(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_withoutprompt(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -67,7 +69,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_withseed(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_withseed(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -90,7 +93,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_withoutseed(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_withoutseed(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -111,7 +115,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_usedelta(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_usedelta(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=True, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -135,7 +140,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_usenondelta(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_usenondelta(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -159,7 +165,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_fallbackonundefined(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_fallbackonundefined(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -181,7 +188,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_cn(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_cn(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -203,7 +211,8 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
|
|
||||||
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
def test_cn_fallback(self, mock_deformanimkeys, mock_controlnetkeys):
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
def test_cn_fallback(self, mock_deformanimkeys, mock_controlnetkeys, mock_looperanimkeys):
|
||||||
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
||||||
{
|
{
|
||||||
"options": {
|
"options": {
|
||||||
|
|
@ -223,5 +232,51 @@ class TestParseqAnimKeys(unittest.TestCase):
|
||||||
#There must be a better way to inject an expected value via patch and check for that...
|
#There must be a better way to inject an expected value via patch and check for that...
|
||||||
self.assertRegex(str(parseq_adapter.cn_keys.cn_1_weight_schedule_series[0]), r'MagicMock')
|
self.assertRegex(str(parseq_adapter.cn_keys.cn_1_weight_schedule_series[0]), r'MagicMock')
|
||||||
|
|
||||||
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
|
def test_looper(self, mock_deformanimkeys, mock_looperanimkeys, mock_controlnetkeys):
|
||||||
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"output_fps": 30
|
||||||
|
},
|
||||||
|
"rendered_frames": [
|
||||||
|
{
|
||||||
|
"frame": 0,
|
||||||
|
"guided_blendFactorMax": 0.4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": 1,
|
||||||
|
"guided_blendFactorMax": 0.4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
self.assertEqual(parseq_adapter.looper_keys.blendFactorMax_series[0], 0.4)
|
||||||
|
|
||||||
|
@patch('deforum_helpers.parseq_adapter.DeformAnimKeys')
|
||||||
|
@patch('deforum_helpers.parseq_adapter.LooperAnimKeys')
|
||||||
|
@patch('deforum_helpers.parseq_adapter.ControlNetKeys')
|
||||||
|
def test_looper_fallback(self, mock_deformanimkeys, mock_looperanimkeys, mock_controlnetkeys):
|
||||||
|
parseq_adapter = buildParseqAdapter(parseq_use_deltas=False, parseq_manifest="""
|
||||||
|
{
|
||||||
|
"options": {
|
||||||
|
"output_fps": 30
|
||||||
|
},
|
||||||
|
"rendered_frames": [
|
||||||
|
{
|
||||||
|
"frame": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"frame": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
#TODO - this is a hacky check to make sure we're falling back to the mock.
|
||||||
|
#There must be a better way to inject an expected value via patch and check for that...
|
||||||
|
self.assertRegex(str(parseq_adapter.looper_keys.blendFactorMax_series[0]), r'MagicMock')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
@ -12,7 +12,7 @@ from .generate import generate, isJson
|
||||||
from .noise import add_noise
|
from .noise import add_noise
|
||||||
from .animation import anim_frame_warp
|
from .animation import anim_frame_warp
|
||||||
from .animation_key_frames import DeformAnimKeys, LooperAnimKeys
|
from .animation_key_frames import DeformAnimKeys, LooperAnimKeys
|
||||||
from .video_audio_utilities import get_frame_name, get_next_frame
|
from .video_audio_utilities import get_frame_name, get_next_frame, render_preview
|
||||||
from .depth import DepthModel
|
from .depth import DepthModel
|
||||||
from .colors import maintain_colors
|
from .colors import maintain_colors
|
||||||
from .parseq_adapter import ParseqAdapter
|
from .parseq_adapter import ParseqAdapter
|
||||||
|
|
@ -63,11 +63,11 @@ def render_animation(args, anim_args, video_args, parseq_args, loop_args, contro
|
||||||
unpack_controlnet_vids(args, anim_args, controlnet_args)
|
unpack_controlnet_vids(args, anim_args, controlnet_args)
|
||||||
|
|
||||||
# initialise Parseq adapter
|
# initialise Parseq adapter
|
||||||
parseq_adapter = ParseqAdapter(parseq_args, anim_args, video_args, controlnet_args)
|
parseq_adapter = ParseqAdapter(parseq_args, anim_args, video_args, controlnet_args, loop_args)
|
||||||
|
|
||||||
# expand key frame strings to values
|
# expand key frame strings to values
|
||||||
keys = DeformAnimKeys(anim_args, args.seed) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
|
keys = DeformAnimKeys(anim_args, args.seed) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
|
||||||
loopSchedulesAndData = LooperAnimKeys(loop_args, anim_args, args.seed)
|
loopSchedulesAndData = LooperAnimKeys(loop_args, anim_args, args.seed) if not parseq_adapter.use_parseq else parseq_adapter.looper_keys
|
||||||
|
|
||||||
# create output folder for the batch
|
# create output folder for the batch
|
||||||
os.makedirs(args.outdir, exist_ok=True)
|
os.makedirs(args.outdir, exist_ok=True)
|
||||||
|
|
@ -192,6 +192,7 @@ def render_animation(args, anim_args, video_args, parseq_args, loop_args, contro
|
||||||
|
|
||||||
# Webui
|
# Webui
|
||||||
state.job_count = anim_args.max_frames
|
state.job_count = anim_args.max_frames
|
||||||
|
last_preview_frame = 0
|
||||||
|
|
||||||
while frame_idx < anim_args.max_frames:
|
while frame_idx < anim_args.max_frames:
|
||||||
# Webui
|
# Webui
|
||||||
|
|
@ -608,8 +609,11 @@ def render_animation(args, anim_args, video_args, parseq_args, loop_args, contro
|
||||||
|
|
||||||
args.seed = next_seed(args, root)
|
args.seed = next_seed(args, root)
|
||||||
|
|
||||||
|
last_preview_frame = render_preview(args, anim_args, video_args, root, frame_idx, last_preview_frame)
|
||||||
|
|
||||||
if predict_depths and not keep_in_vram:
|
if predict_depths and not keep_in_vram:
|
||||||
depth_model.delete_model() # handles adabins too
|
depth_model.delete_model() # handles adabins too
|
||||||
|
|
||||||
if load_raft:
|
if load_raft:
|
||||||
raft_model.delete_model()
|
raft_model.delete_model()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import numexpr
|
||||||
from modules.shared import opts, state
|
from modules.shared import opts, state
|
||||||
from .render import render_animation
|
from .render import render_animation
|
||||||
from .seed import next_seed
|
from .seed import next_seed
|
||||||
from .video_audio_utilities import vid2frames
|
from .video_audio_utilities import vid2frames, render_preview
|
||||||
from .prompt import interpolate_prompts
|
from .prompt import interpolate_prompts
|
||||||
from .generate import generate
|
from .generate import generate
|
||||||
from .animation_key_frames import DeformAnimKeys
|
from .animation_key_frames import DeformAnimKeys
|
||||||
|
|
@ -80,7 +80,7 @@ def get_parsed_value(value, frame_idx, max_f):
|
||||||
def render_interpolation(args, anim_args, video_args, parseq_args, loop_args, controlnet_args, root):
|
def render_interpolation(args, anim_args, video_args, parseq_args, loop_args, controlnet_args, root):
|
||||||
|
|
||||||
# use parseq if manifest is provided
|
# use parseq if manifest is provided
|
||||||
parseq_adapter = ParseqAdapter(parseq_args, anim_args, video_args, controlnet_args)
|
parseq_adapter = ParseqAdapter(parseq_args, anim_args, video_args, controlnet_args, loop_args)
|
||||||
|
|
||||||
# expand key frame strings to values
|
# expand key frame strings to values
|
||||||
keys = DeformAnimKeys(anim_args) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
|
keys = DeformAnimKeys(anim_args) if not parseq_adapter.use_parseq else parseq_adapter.anim_keys
|
||||||
|
|
@ -102,6 +102,7 @@ def render_interpolation(args, anim_args, video_args, parseq_args, loop_args, co
|
||||||
|
|
||||||
state.job_count = anim_args.max_frames
|
state.job_count = anim_args.max_frames
|
||||||
frame_idx = 0
|
frame_idx = 0
|
||||||
|
last_preview_frame = 0
|
||||||
# INTERPOLATION MODE
|
# INTERPOLATION MODE
|
||||||
while frame_idx < anim_args.max_frames:
|
while frame_idx < anim_args.max_frames:
|
||||||
# print data to cli
|
# print data to cli
|
||||||
|
|
@ -155,4 +156,8 @@ def render_interpolation(args, anim_args, video_args, parseq_args, loop_args, co
|
||||||
if args.seed_behavior != 'schedule':
|
if args.seed_behavior != 'schedule':
|
||||||
args.seed = next_seed(args, root)
|
args.seed = next_seed(args, root)
|
||||||
|
|
||||||
|
last_preview_frame = render_preview(args, anim_args, video_args, root, frame_idx, last_preview_frame)
|
||||||
|
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ from .save_images import dump_frames_cache, reset_frames_cache
|
||||||
from .frame_interpolation import process_video_interpolation
|
from .frame_interpolation import process_video_interpolation
|
||||||
from .general_utils import get_deforum_version
|
from .general_utils import get_deforum_version
|
||||||
from .upscaling import make_upscale_v2
|
from .upscaling import make_upscale_v2
|
||||||
from .video_audio_utilities import ffmpeg_stitch_video, make_gifski_gif, handle_imgs_deletion, handle_input_frames_deletion, handle_cn_frames_deletion, get_ffmpeg_params
|
from .video_audio_utilities import ffmpeg_stitch_video, make_gifski_gif, handle_imgs_deletion, handle_input_frames_deletion, handle_cn_frames_deletion, get_ffmpeg_params, get_ffmpeg_paths
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from .settings import save_settings_from_animation_run
|
from .settings import save_settings_from_animation_run
|
||||||
from .deforum_controlnet import num_of_models
|
from .deforum_controlnet import num_of_models
|
||||||
|
|
@ -104,16 +104,6 @@ def run_deforum(*args):
|
||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
real_audio_track = None
|
|
||||||
if video_args.add_soundtrack != 'None':
|
|
||||||
real_audio_track = anim_args.video_init_path if video_args.add_soundtrack == 'Init Video' else video_args.soundtrack_path
|
|
||||||
|
|
||||||
# Establish path of subtitle file
|
|
||||||
if shared.opts.data.get("deforum_save_gen_info_as_srt", False) and shared.opts.data.get("deforum_embed_srt", False):
|
|
||||||
srt_path = os.path.join(args.outdir, f"{root.timestring}.srt")
|
|
||||||
else:
|
|
||||||
srt_path = None
|
|
||||||
|
|
||||||
# Delete folder with duplicated imgs from OS temp folder
|
# Delete folder with duplicated imgs from OS temp folder
|
||||||
shutil.rmtree(root.tmp_deforum_run_duplicated_folder, ignore_errors=True)
|
shutil.rmtree(root.tmp_deforum_run_duplicated_folder, ignore_errors=True)
|
||||||
|
|
||||||
|
|
@ -125,14 +115,11 @@ def run_deforum(*args):
|
||||||
if video_args.skip_video_creation:
|
if video_args.skip_video_creation:
|
||||||
print("\nSkipping video creation, uncheck 'Skip video creation' in 'Output' tab if you want to get a video too :)")
|
print("\nSkipping video creation, uncheck 'Skip video creation' in 'Output' tab if you want to get a video too :)")
|
||||||
else:
|
else:
|
||||||
image_path = os.path.join(args.outdir, f"{root.timestring}_%09d.png")
|
|
||||||
mp4_path = os.path.join(args.outdir, f"{root.timestring}.mp4")
|
|
||||||
max_video_frames = anim_args.max_frames
|
|
||||||
|
|
||||||
# Stitch video using ffmpeg!
|
# Stitch video using ffmpeg!
|
||||||
try:
|
try:
|
||||||
f_location, f_crf, f_preset = get_ffmpeg_params() # get params for ffmpeg exec
|
f_location, f_crf, f_preset = get_ffmpeg_params() # get params for ffmpeg exec
|
||||||
ffmpeg_stitch_video(ffmpeg_location=f_location, fps=video_args.fps, outmp4_path=mp4_path, stitch_from_frame=0, stitch_to_frame=max_video_frames, imgs_path=image_path, add_soundtrack=video_args.add_soundtrack, audio_path=real_audio_track, crf=f_crf, preset=f_preset, srt_path=srt_path)
|
image_path, mp4_path, real_audio_track, srt_path = get_ffmpeg_paths(args.outdir, root.timestring, anim_args, video_args)
|
||||||
|
ffmpeg_stitch_video(ffmpeg_location=f_location, fps=video_args.fps, outmp4_path=mp4_path, stitch_from_frame=0, stitch_to_frame=anim_args.max_frames, imgs_path=image_path, add_soundtrack=video_args.add_soundtrack, audio_path=real_audio_track, crf=f_crf, preset=f_preset, srt_path=srt_path)
|
||||||
mp4 = open(mp4_path, 'rb').read()
|
mp4 = open(mp4_path, 'rb').read()
|
||||||
data_url = f"data:video/mp4;base64, {b64encode(mp4).decode()}"
|
data_url = f"data:video/mp4;base64, {b64encode(mp4).decode()}"
|
||||||
global last_vid_data
|
global last_vid_data
|
||||||
|
|
@ -150,7 +137,7 @@ def run_deforum(*args):
|
||||||
# Upscale video once generation is done:
|
# Upscale video once generation is done:
|
||||||
if video_args.r_upscale_video and not video_args.skip_video_creation and not video_args.store_frames_in_ram:
|
if video_args.r_upscale_video and not video_args.skip_video_creation and not video_args.store_frames_in_ram:
|
||||||
# out mp4 path is defined in make_upscale func
|
# out mp4 path is defined in make_upscale func
|
||||||
make_upscale_v2(upscale_factor = video_args.r_upscale_factor, upscale_model = video_args.r_upscale_model, keep_imgs = video_args.r_upscale_keep_imgs, imgs_raw_path = args.outdir, imgs_batch_id = root.timestring, fps = video_args.fps, deforum_models_path = root.models_path, current_user_os = root.current_user_os, ffmpeg_location=f_location, stitch_from_frame=0, stitch_to_frame=max_video_frames, ffmpeg_crf=f_crf, ffmpeg_preset=f_preset, add_soundtrack = video_args.add_soundtrack ,audio_path=real_audio_track, srt_path=srt_path)
|
make_upscale_v2(upscale_factor = video_args.r_upscale_factor, upscale_model = video_args.r_upscale_model, keep_imgs = video_args.r_upscale_keep_imgs, imgs_raw_path = args.outdir, imgs_batch_id = root.timestring, fps = video_args.fps, deforum_models_path = root.models_path, current_user_os = root.current_user_os, ffmpeg_location=f_location, stitch_from_frame=0, stitch_to_frame=anim_args.max_frames, ffmpeg_crf=f_crf, ffmpeg_preset=f_preset, add_soundtrack = video_args.add_soundtrack ,audio_path=real_audio_track, srt_path=srt_path)
|
||||||
|
|
||||||
# FRAME INTERPOLATION TIME
|
# FRAME INTERPOLATION TIME
|
||||||
if need_to_frame_interpolate:
|
if need_to_frame_interpolate:
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,5 @@ def on_ui_settings():
|
||||||
opts.add_option("deforum_save_gen_info_as_srt", OptionInfo(False, "Save an .srt (subtitles) file with the generation info along with each animation", gr.Checkbox, {"interactive": True}, section=section))
|
opts.add_option("deforum_save_gen_info_as_srt", OptionInfo(False, "Save an .srt (subtitles) file with the generation info along with each animation", gr.Checkbox, {"interactive": True}, section=section))
|
||||||
opts.add_option("deforum_embed_srt", OptionInfo(False, "If .srt file is saved, soft-embed the subtitles into the rendered video file", gr.Checkbox, {"interactive": True}, section=section))
|
opts.add_option("deforum_embed_srt", OptionInfo(False, "If .srt file is saved, soft-embed the subtitles into the rendered video file", gr.Checkbox, {"interactive": True}, section=section))
|
||||||
opts.add_option("deforum_save_gen_info_as_srt_params", OptionInfo(['Noise Schedule'], "Choose which animation params are to be saved to the .srt file (Frame # and Seed will always be saved):", ui_components.DropdownMulti, lambda: {"interactive": True, "choices": srt_ui_params}, section=section))
|
opts.add_option("deforum_save_gen_info_as_srt_params", OptionInfo(['Noise Schedule'], "Choose which animation params are to be saved to the .srt file (Frame # and Seed will always be saved):", ui_components.DropdownMulti, lambda: {"interactive": True, "choices": srt_ui_params}, section=section))
|
||||||
|
opts.add_option("deforum_preview", OptionInfo("Off", "Generate preview video during generation? (Preview does not include frame interpolation or upscaling.)", gr.Dropdown, {"interactive": True, "choices": ['Off', 'On', 'On, concurrent (don\'t pause generation)']}, section=section))
|
||||||
|
opts.add_option("deforum_preview_interval_frames", OptionInfo(100, "Generate preview every N frames", gr.Slider, {"interactive": True, "minimum": 10, "maximum": 500}, section=section))
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ import glob
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from pkg_resources import resource_filename
|
from pkg_resources import resource_filename
|
||||||
from modules.shared import state, opts
|
from modules.shared import state, opts
|
||||||
from .general_utils import checksum, clean_gradio_path_strings
|
from .general_utils import checksum, clean_gradio_path_strings, debug_print
|
||||||
from basicsr.utils.download_util import load_file_from_url
|
from basicsr.utils.download_util import load_file_from_url
|
||||||
from .rich import console
|
from .rich import console
|
||||||
|
import shutil
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
def convert_image(input_path, output_path):
|
def convert_image(input_path, output_path):
|
||||||
# Read the input image
|
# Read the input image
|
||||||
|
|
@ -36,6 +38,20 @@ def get_ffmpeg_params(): # get ffmpeg params from webui's settings -> deforum ta
|
||||||
|
|
||||||
return [f_location, f_crf, f_preset]
|
return [f_location, f_crf, f_preset]
|
||||||
|
|
||||||
|
def get_ffmpeg_paths(outdir, timestring, anim_args, video_args, output_suffix=''):
|
||||||
|
image_path = os.path.join(outdir, f"{timestring}_%09d.png")
|
||||||
|
mp4_path = os.path.join(outdir, f"{timestring}{output_suffix}.mp4")
|
||||||
|
|
||||||
|
real_audio_track = None
|
||||||
|
if video_args.add_soundtrack != 'None':
|
||||||
|
real_audio_track = anim_args.video_init_path if video_args.add_soundtrack == 'Init Video' else video_args.soundtrack_path
|
||||||
|
|
||||||
|
srt_path = None
|
||||||
|
if opts.data.get("deforum_save_gen_info_as_srt", False) and opts.data.get("deforum_embed_srt", False):
|
||||||
|
srt_path = os.path.join(outdir, f"{timestring}.srt")
|
||||||
|
|
||||||
|
return [image_path, mp4_path, real_audio_track, srt_path]
|
||||||
|
|
||||||
# e.g gets 'x2' returns just 2 as int
|
# e.g gets 'x2' returns just 2 as int
|
||||||
def extract_number(string):
|
def extract_number(string):
|
||||||
return int(string[1:]) if len(string) > 1 and string[1:].isdigit() else -1
|
return int(string[1:]) if len(string) > 1 and string[1:].isdigit() else -1
|
||||||
|
|
@ -431,3 +447,35 @@ def count_matching_frames(from_folder, img_batch_id):
|
||||||
|
|
||||||
def get_matching_frame(f, img_batch_id=None):
|
def get_matching_frame(f, img_batch_id=None):
|
||||||
return ('png' in f or 'jpg' in f) and '-' not in f and '_depth_' not in f and ((img_batch_id is not None and f.startswith(img_batch_id) or img_batch_id is None))
|
return ('png' in f or 'jpg' in f) and '-' not in f and '_depth_' not in f and ((img_batch_id is not None and f.startswith(img_batch_id) or img_batch_id is None))
|
||||||
|
|
||||||
|
def render_preview(args, anim_args, video_args, root, frame_idx, last_preview_frame):
|
||||||
|
is_preview_on = "on" in opts.data.get("deforum_preview", "off").lower()
|
||||||
|
preview_interval_frames = opts.data.get("deforum_preview_interval_frames", 50)
|
||||||
|
is_preview_frame = (frame_idx % preview_interval_frames) == 0 or (frame_idx - last_preview_frame) >= preview_interval_frames
|
||||||
|
is_close_to_end = frame_idx >= (anim_args.max_frames-1)
|
||||||
|
|
||||||
|
debug_print(f"render preview video: frame_idx={frame_idx} preview_interval_frames={preview_interval_frames} anim_args.max_frames={anim_args.max_frames} is_preview_on={is_preview_on} is_preview_frame={is_preview_frame} is_close_to_end={is_close_to_end} ")
|
||||||
|
|
||||||
|
if not is_preview_on or not is_preview_frame or is_close_to_end:
|
||||||
|
debug_print(f"No preview video on frame {frame_idx}.")
|
||||||
|
return last_preview_frame
|
||||||
|
|
||||||
|
f_location, f_crf, f_preset = get_ffmpeg_params() # get params for ffmpeg exec
|
||||||
|
image_path, mp4_temp_path, real_audio_track, srt_path = get_ffmpeg_paths(args.outdir, root.timestring, anim_args, video_args, "_preview__rendering__")
|
||||||
|
mp4_preview_path = mp4_temp_path.replace("_preview__rendering__", "_preview")
|
||||||
|
def task():
|
||||||
|
if os.path.exists(mp4_temp_path):
|
||||||
|
print(f"--! Skipping preview video on frame {frame_idx} (previous preview still rendering to {mp4_temp_path}...")
|
||||||
|
else:
|
||||||
|
print(f"--> Rendering preview video up to frame {frame_idx} to {mp4_preview_path}...")
|
||||||
|
try:
|
||||||
|
ffmpeg_stitch_video(ffmpeg_location=f_location, fps=video_args.fps, outmp4_path=mp4_temp_path, stitch_from_frame=0, stitch_to_frame=frame_idx, imgs_path=image_path, add_soundtrack=video_args.add_soundtrack, audio_path=real_audio_track, crf=f_crf, preset=f_preset, srt_path=srt_path)
|
||||||
|
finally:
|
||||||
|
shutil.move(mp4_temp_path, mp4_preview_path)
|
||||||
|
|
||||||
|
if "concurrent" in opts.data.get("deforum_preview", "off").lower():
|
||||||
|
Thread(target=task).start()
|
||||||
|
else:
|
||||||
|
task()
|
||||||
|
|
||||||
|
return frame_idx
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue