Allow Parseq users to use the FPS, cadence and max_frames specified in their Parseq doc (instead of having to duplicate them).

pull/886/head
rewbs 2023-09-19 22:29:40 +10:00
parent f635a2154c
commit 6e4885fe38
7 changed files with 71 additions and 26 deletions

View File

@ -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)",

View File

@ -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():

View File

@ -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 ""

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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()}