Allow Parseq users to use the FPS, cadence and max_frames specified in their Parseq doc (instead of having to duplicate them).
parent
f635a2154c
commit
6e4885fe38
|
|
@ -143,6 +143,7 @@
|
||||||
"hybrid_comp_save_extra_frames": false,
|
"hybrid_comp_save_extra_frames": false,
|
||||||
"parseq_manifest": "",
|
"parseq_manifest": "",
|
||||||
"parseq_use_deltas": true,
|
"parseq_use_deltas": true,
|
||||||
|
"parseq_non_schedule_overrides": true,
|
||||||
"use_looper": false,
|
"use_looper": false,
|
||||||
"init_images": "{\n \"0\": \"https://deforum.github.io/a1/Gi1.png\",\n \"max_f/4-5\": \"https://deforum.github.io/a1/Gi2.png\",\n \"max_f/2-10\": \"https://deforum.github.io/a1/Gi3.png\",\n \"3*max_f/4-15\": \"https://deforum.github.io/a1/Gi4.jpg\",\n \"max_f-20\": \"https://deforum.github.io/a1/Gi1.png\"\n}",
|
"init_images": "{\n \"0\": \"https://deforum.github.io/a1/Gi1.png\",\n \"max_f/4-5\": \"https://deforum.github.io/a1/Gi2.png\",\n \"max_f/2-10\": \"https://deforum.github.io/a1/Gi3.png\",\n \"3*max_f/4-15\": \"https://deforum.github.io/a1/Gi4.jpg\",\n \"max_f-20\": \"https://deforum.github.io/a1/Gi1.png\"\n}",
|
||||||
"image_strength_schedule": "0:(0.75)",
|
"image_strength_schedule": "0:(0.75)",
|
||||||
|
|
|
||||||
|
|
@ -967,16 +967,23 @@ def LoopArgs():
|
||||||
def ParseqArgs():
|
def ParseqArgs():
|
||||||
return {
|
return {
|
||||||
"parseq_manifest": {
|
"parseq_manifest": {
|
||||||
"label": "Parseq Manifest (JSON or URL)",
|
"label": "Parseq manifest (JSON or URL)",
|
||||||
"type": "textbox",
|
"type": "textbox",
|
||||||
"lines": 4,
|
"lines": 4,
|
||||||
"value": None,
|
"value": None,
|
||||||
},
|
},
|
||||||
"parseq_use_deltas": {
|
"parseq_use_deltas": {
|
||||||
"label": "Use delta values for movement parameters",
|
"label": "Use delta values for movement parameters (recommended)",
|
||||||
"type": "checkbox",
|
"type": "checkbox",
|
||||||
"value": True,
|
"value": True,
|
||||||
}
|
"info": "Recommended. If you uncheck this, Parseq keyframe values as are treated as relative movement values instead of absolute."
|
||||||
|
},
|
||||||
|
"parseq_non_schedule_overrides": {
|
||||||
|
"label": "Recommended. If you uncheck this, the FPS, max_frames and cadence in the Parseq doc will be ignored, and the values in the A1111 UI will be used.",
|
||||||
|
"type": "checkbox",
|
||||||
|
"value": True,
|
||||||
|
"info": "Use FPS, max_frames and cadence from the Parseq manifest, if present (recommended)."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def DeforumOutputArgs():
|
def DeforumOutputArgs():
|
||||||
|
|
|
||||||
|
|
@ -141,4 +141,7 @@ def download_file_with_checksum(url, expected_checksum, dest_folder, dest_filena
|
||||||
if not os.path.exists(expected_full_path) and not os.path.isdir(expected_full_path):
|
if not os.path.exists(expected_full_path) and not os.path.isdir(expected_full_path):
|
||||||
load_file_from_url(url=url, model_dir=dest_folder, file_name=dest_filename, progress=True)
|
load_file_from_url(url=url, model_dir=dest_folder, file_name=dest_filename, progress=True)
|
||||||
if checksum(expected_full_path) != expected_checksum:
|
if checksum(expected_full_path) != expected_checksum:
|
||||||
raise Exception(f"Error while downloading {dest_filename}.]nPlease manually download from: {url}\nAnd place it in: {dest_folder}")
|
raise Exception(f"Error while downloading {dest_filename}.]nPlease manually download from: {url}\nAnd place it in: {dest_folder}")
|
||||||
|
|
||||||
|
def tickOrCross(value):
|
||||||
|
return "✅" if value else "❌"
|
||||||
|
|
@ -24,6 +24,7 @@ import pandas as pd
|
||||||
import requests
|
import requests
|
||||||
from .animation_key_frames import DeformAnimKeys, ControlNetKeys, LooperAnimKeys
|
from .animation_key_frames import DeformAnimKeys, ControlNetKeys, LooperAnimKeys
|
||||||
from .rich import console
|
from .rich import console
|
||||||
|
from .general_utils import tickOrCross
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -35,11 +36,31 @@ class ParseqAdapter():
|
||||||
# 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()
|
||||||
self.use_deltas = parseq_args.parseq_use_deltas
|
self.use_deltas = parseq_args.parseq_use_deltas
|
||||||
|
self.non_schedule_overrides = parseq_args.parseq_non_schedule_overrides
|
||||||
|
|
||||||
|
self.video_args = video_args
|
||||||
|
self.anim_args = anim_args
|
||||||
|
|
||||||
self.parseq_json = self.load_manifest(parseq_args) if self.use_parseq else json.loads('{ "rendered_frames": [{"frame": 0}] }')
|
self.parseq_json = self.load_manifest(parseq_args) if self.use_parseq else json.loads('{ "rendered_frames": [{"frame": 0}] }')
|
||||||
self.rendered_frames = self.parseq_json['rendered_frames']
|
self.rendered_frames = self.parseq_json['rendered_frames']
|
||||||
self.max_frame = self.get_max('frame')
|
|
||||||
self.required_frames = anim_args.max_frames
|
# Store the settings from the UI in case we override them, so we can report clearly on what's going on.
|
||||||
|
self.a1111_fps = video_args.fps
|
||||||
|
self.a1111_cadence = anim_args.diffusion_cadence
|
||||||
|
self.a1111_frame_count = anim_args.max_frames
|
||||||
|
|
||||||
|
# These options in the Parseq manifest will override the options used by Deforum _if and only if_ parseq_non_schedule_overrides is true.
|
||||||
|
# Warning: we mutate fields on the actual anim_args and video_args objects
|
||||||
|
if (self.use_parseq):
|
||||||
|
self.fps = self.parseq_json['options']['output_fps'] if 'output_fps' in self.parseq_json['options'] else None
|
||||||
|
self.cadence = self.parseq_json['options']['cadence'] if 'cadence' in self.parseq_json['options'] else None
|
||||||
|
self.frame_count = len(self.rendered_frames)
|
||||||
|
if self.manages_max_frames():
|
||||||
|
anim_args.max_frames = self.frame_count
|
||||||
|
if self.manages_cadence():
|
||||||
|
anim_args.diffusion_cadence = self.cadence
|
||||||
|
if self.manages_fps():
|
||||||
|
video_args.fps = self.fps
|
||||||
|
|
||||||
# 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))
|
||||||
|
|
@ -49,12 +70,10 @@ class ParseqAdapter():
|
||||||
|
|
||||||
# Validation
|
# Validation
|
||||||
if (self.use_parseq):
|
if (self.use_parseq):
|
||||||
self.required_fps = video_args.fps
|
max_frame = self.get_max('frame')
|
||||||
self.config_output_fps = self.parseq_json['options']['output_fps']
|
expected_frame_count = max_frame+1
|
||||||
count_defined_frames = len(self.rendered_frames)
|
if (self.frame_count != expected_frame_count):
|
||||||
expected_defined_frames = self.max_frame+1 # frames are 0-indexed
|
logging.warning(f"There may be duplicated or missing frame data in the Parseq input: expected {expected_frame_count} frames including frame 0 because the highest frame number is {max_frame}, but there are {self.frame_count} frames defined.")
|
||||||
if (expected_defined_frames != count_defined_frames):
|
|
||||||
logging.warning(f"There may be duplicated or missing frame data in the Parseq input: expected {expected_defined_frames} frames including frame 0 because the highest frame number is {self.max_frame}, but there are {count_defined_frames} frames defined.")
|
|
||||||
if not mute:
|
if not mute:
|
||||||
self.print_parseq_table()
|
self.print_parseq_table()
|
||||||
|
|
||||||
|
|
@ -98,24 +117,34 @@ 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("Animation", '\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("ControlNet", '\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:
|
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("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", tickOrCross(self.manages_prompts()), tickOrCross(not self.manages_prompts()))
|
||||||
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(self.frame_count) + tickOrCross(self.manages_max_frames()), str(self.a1111_frame_count) + tickOrCross(not self.manages_max_frames()))
|
||||||
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.fps) + tickOrCross(self.manages_fps()), str(self.a1111_fps) + tickOrCross(not self.manages_fps()))
|
||||||
|
table.add_row("Cadence", str(self.cadence) + tickOrCross(self.manages_cadence()), str(self.a1111_cadence) + tickOrCross(not self.manages_cadence()))
|
||||||
|
|
||||||
console.print("\nUse this table to validate your Parseq & Deforum setup:")
|
console.print("\nUse this table to validate your Parseq & Deforum setup:")
|
||||||
console.print(table)
|
console.print(table)
|
||||||
|
|
||||||
def manages_prompts(self):
|
def manages_prompts(self):
|
||||||
return self.use_parseq and 'deforum_prompt' in self.rendered_frames[0].keys()
|
return self.use_parseq and 'deforum_prompt' in self.rendered_frames[0].keys()
|
||||||
|
|
||||||
def manages_seed(self):
|
def manages_seed(self):
|
||||||
return self.use_parseq and 'seed' in self.rendered_frames[0].keys()
|
return self.use_parseq and 'seed' in self.rendered_frames[0].keys()
|
||||||
|
|
||||||
|
def manages_cadence(self):
|
||||||
|
return self.use_parseq and self.non_schedule_overrides and self.cadence
|
||||||
|
|
||||||
|
def manages_fps(self):
|
||||||
|
return self.use_parseq and self.non_schedule_overrides and self.fps
|
||||||
|
|
||||||
|
def manages_max_frames(self):
|
||||||
|
return self.use_parseq and self.non_schedule_overrides and self.frame_count
|
||||||
|
|
||||||
def get_max(self, seriesName):
|
def get_max(self, seriesName):
|
||||||
return max(self.rendered_frames, key=itemgetter(seriesName))[seriesName]
|
return max(self.rendered_frames, key=itemgetter(seriesName))[seriesName]
|
||||||
|
|
||||||
|
|
@ -135,19 +164,21 @@ class ParseqAbstractDecorator():
|
||||||
logging.debug(f"Found {seriesName} in first frame of Parseq data. Assuming it's defined.")
|
logging.debug(f"Found {seriesName} in first frame of Parseq data. Assuming it's defined.")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
required_frames = self.adapter.anim_args.max_frames
|
||||||
|
|
||||||
key_frame_series = pd.Series([np.nan for a in range(self.adapter.required_frames)])
|
key_frame_series = pd.Series([np.nan for a in range(required_frames)])
|
||||||
|
|
||||||
for frame in self.adapter.rendered_frames:
|
for frame in self.adapter.rendered_frames:
|
||||||
frame_idx = frame['frame']
|
frame_idx = frame['frame']
|
||||||
if frame_idx < self.adapter.required_frames:
|
if frame_idx < required_frames:
|
||||||
if not np.isnan(key_frame_series[frame_idx]):
|
if not np.isnan(key_frame_series[frame_idx]):
|
||||||
logging.warning(f"Duplicate frame definition {frame_idx} detected for data {seriesName}. Latest wins.")
|
logging.warning(f"Duplicate frame definition {frame_idx} detected for data {seriesName}. Latest wins.")
|
||||||
key_frame_series[frame_idx] = frame[seriesName]
|
key_frame_series[frame_idx] = frame[seriesName]
|
||||||
|
|
||||||
# If the animation will have more frames than Parseq defines,
|
# If the animation will have more frames than Parseq defines,
|
||||||
# duplicate final value to match the required frame count.
|
# duplicate final value to match the required frame count.
|
||||||
while (frame_idx < self.adapter.required_frames):
|
while (frame_idx < required_frames):
|
||||||
key_frame_series[frame_idx] = operator.itemgetter(-1)(self.adapter.rendered_frames)[seriesName]
|
key_frame_series[frame_idx] = operator.itemgetter(-1)(self.adapter.rendered_frames)[seriesName]
|
||||||
frame_idx += 1
|
frame_idx += 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ DEFAULT_ARGS = SimpleNamespace(anim_args = SimpleNamespace(max_frames=2),
|
||||||
|
|
||||||
|
|
||||||
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_non_schedule_overrides=False, parseq_manifest=parseq_manifest),
|
||||||
setup_args.anim_args, setup_args.video_args, setup_args.controlnet_args, setup_args.loop_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):
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@ from .RAFT import RAFT
|
||||||
from deforum_api import JobStatusTracker
|
from deforum_api import JobStatusTracker
|
||||||
|
|
||||||
def render_animation(args, anim_args, video_args, parseq_args, loop_args, controlnet_args, root):
|
def render_animation(args, anim_args, video_args, parseq_args, loop_args, controlnet_args, root):
|
||||||
|
|
||||||
|
# initialise Parseq adapter
|
||||||
|
parseq_adapter = ParseqAdapter(parseq_args, anim_args, video_args, controlnet_args, loop_args)
|
||||||
|
|
||||||
if opts.data.get("deforum_save_gen_info_as_srt", False): # create .srt file and set timeframe mechanism using FPS
|
if opts.data.get("deforum_save_gen_info_as_srt", False): # create .srt file and set timeframe mechanism using FPS
|
||||||
srt_filename = os.path.join(args.outdir, f"{root.timestring}.srt")
|
srt_filename = os.path.join(args.outdir, f"{root.timestring}.srt")
|
||||||
srt_frame_duration = init_srt_file(srt_filename, video_args.fps)
|
srt_frame_duration = init_srt_file(srt_filename, video_args.fps)
|
||||||
|
|
@ -80,9 +84,6 @@ def render_animation(args, anim_args, video_args, parseq_args, loop_args, contro
|
||||||
if is_controlnet_enabled(controlnet_args):
|
if is_controlnet_enabled(controlnet_args):
|
||||||
unpack_controlnet_vids(args, anim_args, controlnet_args)
|
unpack_controlnet_vids(args, anim_args, controlnet_args)
|
||||||
|
|
||||||
# initialise Parseq adapter
|
|
||||||
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) if not parseq_adapter.use_parseq else parseq_adapter.looper_keys
|
loopSchedulesAndData = LooperAnimKeys(loop_args, anim_args, args.seed) if not parseq_adapter.use_parseq else parseq_adapter.looper_keys
|
||||||
|
|
|
||||||
|
|
@ -352,6 +352,8 @@ def get_tab_init(d, da, dp):
|
||||||
gr.HTML(value=get_gradio_html('parseq'))
|
gr.HTML(value=get_gradio_html('parseq'))
|
||||||
with FormRow():
|
with FormRow():
|
||||||
parseq_manifest = create_gr_elem(dp.parseq_manifest)
|
parseq_manifest = create_gr_elem(dp.parseq_manifest)
|
||||||
|
with FormRow():
|
||||||
|
parseq_non_schedule_overrides = create_gr_elem(dp.parseq_non_schedule_overrides)
|
||||||
with FormRow():
|
with FormRow():
|
||||||
parseq_use_deltas = create_gr_elem(dp.parseq_use_deltas)
|
parseq_use_deltas = create_gr_elem(dp.parseq_use_deltas)
|
||||||
return {k: v for k, v in {**locals(), **vars()}.items()}
|
return {k: v for k, v in {**locals(), **vars()}.items()}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue