Spaces:
Runtime error
Runtime error
File size: 9,466 Bytes
6cf4883 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
import copy
import json
import logging
import operator
from operator import itemgetter
import numpy as np
import pandas as pd
import requests
from .animation_key_frames import DeformAnimKeys
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
class ParseqAnimKeys():
def __init__(self, parseq_args, anim_args):
# Resolve manifest either directly from supplied value
# or via supplied URL
manifestOrUrl = parseq_args.parseq_manifest.strip()
if (manifestOrUrl.startswith('http')):
logging.info(f"Loading Parseq manifest from URL: {manifestOrUrl}")
try:
body = requests.get(manifestOrUrl).text
logging.debug(f"Loaded remote manifest: {body}")
self.parseq_json = json.loads(body)
# Add the parseq manifest without the detailed frame data to parseq_args.
# This ensures it will be saved in the settings file, so that you can always
# see exactly what parseq prompts and keyframes were used, even if what the URL
# points to changes.
parseq_args.fetched_parseq_manifest_summary = copy.deepcopy(self.parseq_json)
if parseq_args.fetched_parseq_manifest_summary['rendered_frames']:
del parseq_args.fetched_parseq_manifest_summary['rendered_frames']
if parseq_args.fetched_parseq_manifest_summary['rendered_frames_meta']:
del parseq_args.fetched_parseq_manifest_summary['rendered_frames_meta']
except Exception as e:
logging.error(f"Unable to load Parseq manifest from URL: {manifestOrUrl}")
raise e
else:
self.parseq_json = json.loads(manifestOrUrl)
self.default_anim_keys = DeformAnimKeys(anim_args)
self.rendered_frames = self.parseq_json['rendered_frames']
self.max_frame = self.get_max('frame')
count_defined_frames = len(self.rendered_frames)
expected_defined_frames = self.max_frame+1 # frames are 0-indexed
self.required_frames = anim_args.max_frames
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 (anim_args.max_frames > count_defined_frames):
logging.info(f"Parseq data defines {count_defined_frames} frames, but the requested animation is {anim_args.max_frames} frames. The last Parseq frame definition will be duplicated to match the expected frame count.")
if (anim_args.max_frames < count_defined_frames):
logging.info(f"Parseq data defines {count_defined_frames} frames, but the requested animation is {anim_args.max_frames} frames. The last Parseq frame definitions will be ignored.")
else:
logging.info(f"Parseq data defines {count_defined_frames} frames.")
# Parseq treats input values as absolute values. So if you want to
# progressively rotate 180 degrees over 4 frames, you specify: 45, 90, 135, 180.
# However, many animation parameters are relative to the previous frame if there is enough
# loopback strength. So if you want to rotate 180 degrees over 5 frames, the animation engine expects:
# 45, 45, 45, 45. Therefore, for such parameter, we use the fact that Parseq supplies delta values.
optional_delta = '_delta' if parseq_args.parseq_use_deltas else ''
self.angle_series = self.parseq_to_anim_series('angle' + optional_delta)
self.zoom_series = self.parseq_to_anim_series('zoom' + optional_delta)
self.translation_x_series = self.parseq_to_anim_series('translation_x' + optional_delta)
self.translation_y_series = self.parseq_to_anim_series('translation_y' + optional_delta)
self.translation_z_series = self.parseq_to_anim_series('translation_z' + optional_delta)
self.rotation_3d_x_series = self.parseq_to_anim_series('rotation_3d_x' + optional_delta)
self.rotation_3d_y_series = self.parseq_to_anim_series('rotation_3d_y' + optional_delta)
self.rotation_3d_z_series = self.parseq_to_anim_series('rotation_3d_z' + optional_delta)
self.perspective_flip_theta_series = self.parseq_to_anim_series('perspective_flip_theta' + optional_delta)
self.perspective_flip_phi_series = self.parseq_to_anim_series('perspective_flip_phi' + optional_delta)
self.perspective_flip_gamma_series = self.parseq_to_anim_series('perspective_flip_gamma' + optional_delta)
# Non-motion animation args
self.perspective_flip_fv_series = self.parseq_to_anim_series('perspective_flip_fv')
self.noise_schedule_series = self.parseq_to_anim_series('noise')
self.strength_schedule_series = self.parseq_to_anim_series('strength')
self.sampler_schedule_series = self.parseq_to_anim_series('sampler_schedule')
self.contrast_schedule_series = self.parseq_to_anim_series('contrast')
self.cfg_scale_schedule_series = self.parseq_to_anim_series('scale')
self.steps_schedule_series = self.parseq_to_anim_series("steps_schedule")
self.seed_schedule_series = self.parseq_to_anim_series('seed')
self.fov_series = self.parseq_to_anim_series('fov')
self.near_series = self.parseq_to_anim_series('near')
self.far_series = self.parseq_to_anim_series('far')
self.prompts = self.parseq_to_anim_series('deforum_prompt') # formatted as "{positive} --neg {negative}"
self.subseed_series = self.parseq_to_anim_series('subseed')
self.subseed_strength_series = self.parseq_to_anim_series('subseed_strength')
self.kernel_schedule_series = self.parseq_to_anim_series('antiblur_kernel')
self.sigma_schedule_series = self.parseq_to_anim_series('antiblur_sigma')
self.amount_schedule_series = self.parseq_to_anim_series('antiblur_amount')
self.threshold_schedule_series = self.parseq_to_anim_series('antiblur_threshold')
# Config:
# TODO this is currently ignored. User must ensure the output FPS set in parseq
# matches the one set in Deforum to avoid unexpected results.
self.config_output_fps = self.parseq_json['options']['output_fps']
def get_max(self, seriesName):
return max(self.rendered_frames, key=itemgetter(seriesName))[seriesName]
def parseq_to_anim_series(self, seriesName):
# Check if valus is present in first frame of JSON data. If not, assume it's undefined.
# The Parseq contract is that the first frame (at least) must define values for all fields.
try:
if self.rendered_frames[0][seriesName] is not None:
logging.info(f"Found {seriesName} in first frame of Parseq data. Assuming it's defined.")
except KeyError:
return None
key_frame_series = pd.Series([np.nan for a in range(self.required_frames)])
for frame in self.rendered_frames:
frame_idx = frame['frame']
if frame_idx < self.required_frames:
if not np.isnan(key_frame_series[frame_idx]):
logging.warning(f"Duplicate frame definition {frame_idx} detected for data {seriesName}. Latest wins.")
key_frame_series[frame_idx] = frame[seriesName]
# If the animation will have more frames than Parseq defines,
# duplicate final value to match the required frame count.
while (frame_idx < self.required_frames):
key_frame_series[frame_idx] = operator.itemgetter(-1)(self.rendered_frames)[seriesName]
frame_idx += 1
return key_frame_series
# fallback to anim_args if the series is not defined in the Parseq data
def __getattribute__(inst, name):
try:
definedField = super(ParseqAnimKeys, inst).__getattribute__(name)
except AttributeError:
# No field with this name has been explicitly extracted from the JSON data.
# It must be a new parameter. Let's see if it's in the raw JSON.
# parseq doesn't use _series, _schedule or _schedule_series suffixes in the
# JSON data - remove them.
strippableSuffixes = ['_series', '_schedule']
parseqName = name
while any(parseqName.endswith(suffix) for suffix in strippableSuffixes):
for suffix in strippableSuffixes:
if parseqName.endswith(suffix):
parseqName = parseqName[:-len(suffix)]
# returns None if not defined in Parseq JSON data
definedField = inst.parseq_to_anim_series(parseqName)
if (definedField is not None):
# add the field to the instance so we don't compute it again.
setattr(inst, name, definedField)
if (definedField is not None):
return definedField
else:
logging.info(f"Data for {name} not defined in Parseq data (looked for: '{parseqName}'). Falling back to standard Deforum values.")
return getattr(inst.default_anim_keys, name)
|