diffusers-workflow 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
- diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
- diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
- diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
- dw/__init__.py +440 -0
- dw/adapter_compatibility.py +226 -0
- dw/arguments.py +1231 -0
- dw/assessment_rules.py +159 -0
- dw/assets.py +130 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +146 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/content_types.py +150 -0
- dw/dissolve_frame_errors.py +121 -0
- dw/docs/ACCELERATION.md +352 -0
- dw/docs/AGENT_LOOP.md +95 -0
- dw/docs/DEPENDENCIES.md +91 -0
- dw/docs/IP_ADAPTER.md +109 -0
- dw/docs/LORAS.md +131 -0
- dw/docs/MCP.md +517 -0
- dw/docs/PROMPT_WEIGHTING.md +78 -0
- dw/docs/QUANTIZATION.md +230 -0
- dw/docs/RECIPES_24GB.md +201 -0
- dw/docs/RELEASING.md +195 -0
- dw/docs/REMOTE.md +140 -0
- dw/docs/REPL_COMMANDS.md +121 -0
- dw/docs/REPL_WORKER_GUIDE.md +51 -0
- dw/docs/SECURITY.md +272 -0
- dw/docs/SECURITY_QUICKREF.md +112 -0
- dw/docs/SERVER.md +679 -0
- dw/docs/TASKS.md +1741 -0
- dw/docs/TESTING.md +71 -0
- dw/docs/WORKFLOW_GUIDE.md +2038 -0
- dw/docs/WORKSPACES.md +316 -0
- dw/download_watch.py +335 -0
- dw/elision.py +306 -0
- dw/events.py +275 -0
- dw/for_each.py +409 -0
- dw/host_memory.py +258 -0
- dw/host_memory_projection.py +230 -0
- dw/hub_cache.py +432 -0
- dw/introspection.py +1228 -0
- dw/kernel_availability.py +208 -0
- dw/locations.py +599 -0
- dw/log_setup.py +45 -0
- dw/loudness.py +82 -0
- dw/media_audio.py +217 -0
- dw/media_frames.py +367 -0
- dw/media_info.py +297 -0
- dw/pipeline_processors/chain.py +821 -0
- dw/pipeline_processors/config_objects.py +237 -0
- dw/pipeline_processors/pipeline.py +2297 -0
- dw/pipeline_processors/remote.py +46 -0
- dw/plan.py +920 -0
- dw/previous_results.py +411 -0
- dw/probe_paths.py +59 -0
- dw/prompt_schema.json +48 -0
- dw/prompt_weighting.py +378 -0
- dw/prompts.py +159 -0
- dw/realize.py +250 -0
- dw/reference_limits.py +215 -0
- dw/reference_names.py +125 -0
- dw/repl.py +338 -0
- dw/repl_commands.py +836 -0
- dw/repl_worker.py +159 -0
- dw/result.py +1720 -0
- dw/result_fps.py +82 -0
- dw/run.py +162 -0
- dw/runs.py +768 -0
- dw/scalar_result_validation.py +97 -0
- dw/schema.py +283 -0
- dw/security.py +1038 -0
- dw/select_validation.py +115 -0
- dw/serve.py +277 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +4586 -0
- dw/server/assess.py +132 -0
- dw/server/catalog_shape.py +487 -0
- dw/server/enhancers.py +129 -0
- dw/server/exports.py +480 -0
- dw/server/guides.py +257 -0
- dw/server/jobs.py +1561 -0
- dw/server/mcp_mount.py +95 -0
- dw/server/netinfo.py +124 -0
- dw/server/observed_cost.py +379 -0
- dw/server/sysinfo.py +71 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-PhsdjHSr.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
- dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
- dw/server/ui/assets/index-DgrYhQd9.js +43 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-Bcn70HdC.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-D1HmNnby.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
- dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
- dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/server/updater.py +192 -0
- dw/settings.py +98 -0
- dw/shot_span_preflight.py +116 -0
- dw/shots.py +359 -0
- dw/slice_preflight.py +148 -0
- dw/step.py +187 -0
- dw/step_cache.py +442 -0
- dw/subfolders.py +107 -0
- dw/task_domains.py +307 -0
- dw/tasks/assess.py +826 -0
- dw/tasks/audio_transcription.py +88 -0
- dw/tasks/audio_utils.py +1862 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/compose_text.py +74 -0
- dw/tasks/concat_videos.py +300 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/dissolve_videos.py +342 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +173 -0
- dw/tasks/grade.py +97 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +764 -0
- dw/tasks/interpolate_frames.py +252 -0
- dw/tasks/judge.py +68 -0
- dw/tasks/model_cache.py +55 -0
- dw/tasks/pair_audio.py +268 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/select.py +111 -0
- dw/tasks/speech_generation.py +228 -0
- dw/tasks/stabilize.py +129 -0
- dw/tasks/task.py +920 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +169 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +624 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +381 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +231 -0
- dw/validate.py +68 -0
- dw/variable_constraints.py +444 -0
- dw/variables.py +443 -0
- dw/video_extensions.py +141 -0
- dw/vram_estimate.py +116 -0
- dw/worker.py +764 -0
- dw/workflow.py +2007 -0
- dw/workflow_schema.json +1346 -0
- dw/workflow_sources.py +383 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
- dw/workspace.py +730 -0
- dw_mcp/__init__.py +6 -0
- dw_mcp/__main__.py +133 -0
- dw_mcp/assets.py +336 -0
- dw_mcp/authoring.py +114 -0
- dw_mcp/catalog.py +360 -0
- dw_mcp/client.py +486 -0
- dw_mcp/diagnose.py +371 -0
- dw_mcp/exports.py +84 -0
- dw_mcp/guides.py +35 -0
- dw_mcp/media.py +638 -0
- dw_mcp/models.py +97 -0
- dw_mcp/prompts.py +104 -0
- dw_mcp/server.py +1343 -0
- dw_mcp/workspaces.py +212 -0
dw/slice_preflight.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""A `slice_audio` step whose source duration validate can already learn,
|
|
2
|
+
sliced past where that source ends, warned about before the run (#402).
|
|
3
|
+
|
|
4
|
+
`slice_audio` zero-pads a slice that reaches past its source and only says so
|
|
5
|
+
at run time (`_warn_on_slice_past_end`, `dw/tasks/audio_utils.py`) - a
|
|
6
|
+
correct message, but late when the slice sits downstream of a long render
|
|
7
|
+
(#402's repro: `assemble-and-score` scoring a 15.5 s cut with a 4.96 s
|
|
8
|
+
`score` asset, `validate_workflow` answering clean). Mirrors
|
|
9
|
+
`dissolve_frame_errors.py` (#400): walk the expanded definition,
|
|
10
|
+
`resolve_path_references` an `asset:`/`output:` audio into a real path, and
|
|
11
|
+
`probe_media` it - the same resolution and decode the run itself would do,
|
|
12
|
+
just ahead of the queue.
|
|
13
|
+
|
|
14
|
+
Deliberately narrower than the run-time check, same as #400's: a
|
|
15
|
+
`previous_result:` audio (nothing written yet), a remote URL, a literal path
|
|
16
|
+
outside the directories the run may read (`dw/probe_paths.py` - a literal
|
|
17
|
+
inside them is resolved against the workflow's directory and probed), or a
|
|
18
|
+
source `probe_media` cannot read, all answer "unknown" rather than
|
|
19
|
+
guessing - silence here is correct, not a gap, since the run-time warning
|
|
20
|
+
still fires once the file exists. `variable:` needs no hop of its own: by the
|
|
21
|
+
time `validation_errors`/`adapter_warnings` hand this module the *expanded*
|
|
22
|
+
definition, `replace_variables` has already substituted every `variable:`
|
|
23
|
+
reference (or the run cannot start at all), so what is left unresolved is
|
|
24
|
+
only a reference that genuinely cannot resolve yet. The threshold
|
|
25
|
+
(`SLICE_PAD_WARN_MS`) and the requested-length arithmetic mirror
|
|
26
|
+
`slice_audio`'s own two argument shapes, so the two agree on the same
|
|
27
|
+
padding for the same arguments.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from .for_each import MEMBER_SEPARATOR, render_path
|
|
31
|
+
from .media_info import probe_media
|
|
32
|
+
from .probe_paths import resolve_probe_path
|
|
33
|
+
from .tasks.audio_utils import SLICE_PAD_WARN_MS
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _source_seconds(path):
|
|
37
|
+
"""The duration `slice_audio` would see for this file, or None when it
|
|
38
|
+
cannot be probed or carries no audio."""
|
|
39
|
+
info = probe_media(path)
|
|
40
|
+
if info is None or info.get("kind") != "audio":
|
|
41
|
+
return None
|
|
42
|
+
return info.get("duration_seconds")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _as_number(value):
|
|
46
|
+
if isinstance(value, bool) or not isinstance(value, (int, float, str)):
|
|
47
|
+
return None
|
|
48
|
+
try:
|
|
49
|
+
return float(value)
|
|
50
|
+
except (TypeError, ValueError):
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _requested_region(task_args):
|
|
55
|
+
"""The (start_seconds, length_seconds) `slice_audio` would compute for
|
|
56
|
+
these arguments, or None when the shape given cannot be resolved to a
|
|
57
|
+
length without running anything - mirrors `slice_audio`'s own branch
|
|
58
|
+
order in `dw/tasks/audio_utils.py`."""
|
|
59
|
+
start_seconds = _as_number(task_args.get("start_seconds"))
|
|
60
|
+
duration_seconds = _as_number(task_args.get("duration_seconds"))
|
|
61
|
+
start_frame = _as_number(task_args.get("start_frame"))
|
|
62
|
+
num_frames = _as_number(task_args.get("num_frames"))
|
|
63
|
+
fps = _as_number(task_args.get("fps"))
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
task_args.get("start_seconds") is not None
|
|
67
|
+
or task_args.get("duration_seconds") is not None
|
|
68
|
+
):
|
|
69
|
+
if duration_seconds is None:
|
|
70
|
+
# Runs to the source's own end - cannot overrun it
|
|
71
|
+
return None
|
|
72
|
+
return start_seconds or 0.0, duration_seconds
|
|
73
|
+
if (
|
|
74
|
+
task_args.get("start_frame") is not None
|
|
75
|
+
or task_args.get("num_frames") is not None
|
|
76
|
+
):
|
|
77
|
+
if num_frames is None or not fps:
|
|
78
|
+
return None
|
|
79
|
+
return (start_frame or 0.0) / fps, num_frames / fps
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def slice_past_end_warnings(workflow_definition, source_indices=None, base_dir=None):
|
|
84
|
+
"""Every `slice_audio` step whose source's real duration is already
|
|
85
|
+
knowable and whose requested slice reaches past it, as messages.
|
|
86
|
+
|
|
87
|
+
Walks the substituted, expanded definition, the same convention
|
|
88
|
+
`dissolve_frame_errors` follows: `source_indices` maps an expanded step
|
|
89
|
+
back to the one the author wrote, and a path inside a `for_each` member
|
|
90
|
+
names the member.
|
|
91
|
+
"""
|
|
92
|
+
steps = workflow_definition.get("steps")
|
|
93
|
+
if not isinstance(steps, list):
|
|
94
|
+
return []
|
|
95
|
+
|
|
96
|
+
warnings = []
|
|
97
|
+
for index, step in enumerate(steps):
|
|
98
|
+
if not isinstance(step, dict):
|
|
99
|
+
continue
|
|
100
|
+
task = step.get("task")
|
|
101
|
+
if not isinstance(task, dict) or task.get("command") != "slice_audio":
|
|
102
|
+
continue
|
|
103
|
+
task_args = task.get("arguments")
|
|
104
|
+
if not isinstance(task_args, dict):
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
path = resolve_probe_path(task_args.get("audio"), base_dir, "an audio argument")
|
|
108
|
+
if path is None:
|
|
109
|
+
continue
|
|
110
|
+
source_seconds = _source_seconds(path)
|
|
111
|
+
if not source_seconds:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
region = _requested_region(task_args)
|
|
115
|
+
if region is None:
|
|
116
|
+
continue
|
|
117
|
+
requested_start, requested_length = region
|
|
118
|
+
|
|
119
|
+
available = max(0.0, min(source_seconds - requested_start, requested_length))
|
|
120
|
+
padded_seconds = requested_length - available
|
|
121
|
+
if padded_seconds * 1000.0 < SLICE_PAD_WARN_MS:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
source = (
|
|
125
|
+
source_indices[index]
|
|
126
|
+
if source_indices is not None and index < len(source_indices)
|
|
127
|
+
else index
|
|
128
|
+
)
|
|
129
|
+
name = step.get("name")
|
|
130
|
+
where = (
|
|
131
|
+
f" in member '{name}'"
|
|
132
|
+
if isinstance(name, str) and MEMBER_SEPARATOR in name
|
|
133
|
+
else ""
|
|
134
|
+
)
|
|
135
|
+
path_str = render_path(("steps", source, "task", "arguments", "audio"))
|
|
136
|
+
warnings.append(
|
|
137
|
+
f"{path_str}: slice_audio will run {padded_seconds:.2f} s past "
|
|
138
|
+
f"the end of a {source_seconds:.2f} s source ({task_args.get('audio')}), "
|
|
139
|
+
f"so that much of the {requested_start + requested_length:.2f} s "
|
|
140
|
+
f"requested will be digital silence{where}. If you meant to fill "
|
|
141
|
+
f"a cut of this length, make a bed with the 'loop_audio' task "
|
|
142
|
+
f"('target_frames' + 'fps' matches one exactly) and slice that; "
|
|
143
|
+
f"if you meant the tail pad, nothing is wrong."
|
|
144
|
+
)
|
|
145
|
+
return warnings
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
__all__ = ["slice_past_end_warnings"]
|
dw/step.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from .events import WorkflowCancelled, get_context
|
|
3
|
+
from .result import Result
|
|
4
|
+
from .previous_results import get_iterations, resolve_chain_prompts
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger("dw")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Step:
|
|
10
|
+
"""
|
|
11
|
+
Represents a single step in a workflow execution.
|
|
12
|
+
Manages execution of pipelines, tasks, or sub-workflows with their configurations.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
step_definition,
|
|
18
|
+
default_seed,
|
|
19
|
+
workflow_definition=None,
|
|
20
|
+
consumed_by_normalizer=False,
|
|
21
|
+
):
|
|
22
|
+
"""Initialize step with its configuration and seed value.
|
|
23
|
+
|
|
24
|
+
workflow_definition, when given, is the original (unsubstituted)
|
|
25
|
+
definition of the workflow this step belongs to - embedded metadata
|
|
26
|
+
carries it so a saved image can be reopened as the workflow that
|
|
27
|
+
made it.
|
|
28
|
+
|
|
29
|
+
consumed_by_normalizer is whether a later step resets this result's
|
|
30
|
+
level (normalize_audio/match_levels) before anything ships it - see
|
|
31
|
+
step_cache.normalized_downstream and result.py's headroom checks."""
|
|
32
|
+
self.step_definition = step_definition
|
|
33
|
+
self.workflow_definition = workflow_definition
|
|
34
|
+
self.consumed_by_normalizer = consumed_by_normalizer
|
|
35
|
+
self.iteration = None
|
|
36
|
+
|
|
37
|
+
# Get step-specific seed or use default if not specified
|
|
38
|
+
self.default_seed = self.step_definition.get("seed", default_seed)
|
|
39
|
+
logger.debug(f"Initialized step: {self.name} with seed: {self.default_seed}")
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self):
|
|
43
|
+
return self.step_definition.get("name", "unknown")
|
|
44
|
+
|
|
45
|
+
def run(self, previous_results, previous_pipelines, step_action):
|
|
46
|
+
"""
|
|
47
|
+
Execute the step's action with all possible argument combinations.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
previous_results: Results from previous steps, used for argument generation
|
|
51
|
+
previous_pipelines: Previously created pipelines that might be referenced
|
|
52
|
+
step_action: The actual action to execute (Pipeline/Task/Workflow)
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
step_name = self.step_definition["name"]
|
|
56
|
+
logger.debug(f"Starting execution of step: {step_name}")
|
|
57
|
+
|
|
58
|
+
# Create result container with any special configuration from step definition
|
|
59
|
+
# This handles how results should be saved/processed
|
|
60
|
+
result = Result(
|
|
61
|
+
self.step_definition.get("result", {}),
|
|
62
|
+
consumed_by_normalizer=self.consumed_by_normalizer,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Collect metadata for embedding if enabled
|
|
66
|
+
result_def = self.step_definition.get("result", {})
|
|
67
|
+
if result_def.get("embed_metadata", False):
|
|
68
|
+
metadata = self._collect_metadata()
|
|
69
|
+
result.set_metadata(metadata)
|
|
70
|
+
|
|
71
|
+
# Log what type of action we're executing (Pipeline/Task/Workflow)
|
|
72
|
+
action_type = type(step_action).__name__
|
|
73
|
+
logger.info(f"Running {action_type} {step_name}:{step_action.name}...")
|
|
74
|
+
|
|
75
|
+
# A chained pipeline's per-segment prompts live outside the argument
|
|
76
|
+
# template, so they are resolved here rather than by the pass below
|
|
77
|
+
resolve_chain_prompts(step_action, previous_results)
|
|
78
|
+
|
|
79
|
+
# Get all possible argument combinations for this step
|
|
80
|
+
# This expands any references to previous results into concrete values
|
|
81
|
+
iterations = get_iterations(step_action.argument_template, previous_results)
|
|
82
|
+
logger.debug(f"Generated {len(iterations)} argument combinations")
|
|
83
|
+
|
|
84
|
+
# The metadata above was read from the definition, where an
|
|
85
|
+
# argument may still be a 'previous_result:' reference - now that
|
|
86
|
+
# the iterations exist, record what the step actually ran with
|
|
87
|
+
if result.metadata is not None:
|
|
88
|
+
_realize_metadata_arguments(result.metadata, iterations)
|
|
89
|
+
|
|
90
|
+
# Execute the action for each set of arguments
|
|
91
|
+
if not iterations:
|
|
92
|
+
logger.warning(f"Step {step_name} has no iterations to execute")
|
|
93
|
+
return result
|
|
94
|
+
|
|
95
|
+
run_context = get_context()
|
|
96
|
+
for i, arguments in enumerate(iterations, 1):
|
|
97
|
+
run_context.check_cancelled()
|
|
98
|
+
logger.debug(
|
|
99
|
+
f"Running iteration {i}/{len(iterations)} with arguments: {arguments}"
|
|
100
|
+
)
|
|
101
|
+
run_context.emit(
|
|
102
|
+
"iteration_start",
|
|
103
|
+
step=step_name,
|
|
104
|
+
iteration=i,
|
|
105
|
+
total_iterations=len(iterations),
|
|
106
|
+
)
|
|
107
|
+
self.iteration = i
|
|
108
|
+
iteration_result = step_action.run(arguments, previous_pipelines)
|
|
109
|
+
result.add_result(iteration_result)
|
|
110
|
+
|
|
111
|
+
logger.debug(f"Successfully completed step: {step_name}")
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
except WorkflowCancelled:
|
|
115
|
+
logger.info(f"Step {self.name} cancelled")
|
|
116
|
+
raise
|
|
117
|
+
except Exception as e:
|
|
118
|
+
# One log line with the full traceback - callers decide handling.
|
|
119
|
+
# (Configuration, I/O, runtime and unexpected errors all logged and
|
|
120
|
+
# re-raised identically, so one clause replaces the old four)
|
|
121
|
+
iteration = getattr(self, "iteration", None)
|
|
122
|
+
where = f"iteration {iteration} of " if iteration else ""
|
|
123
|
+
logger.error(
|
|
124
|
+
f"{type(e).__name__} in {where}step {self.name}: {e}",
|
|
125
|
+
exc_info=True,
|
|
126
|
+
)
|
|
127
|
+
raise
|
|
128
|
+
|
|
129
|
+
def _collect_metadata(self):
|
|
130
|
+
"""Collect step metadata for embedding in saved images."""
|
|
131
|
+
metadata = {"step_name": self.name}
|
|
132
|
+
|
|
133
|
+
# The whole recipe, not just this step's slice of it - this is what
|
|
134
|
+
# lets a gallery open an image as the workflow that produced it,
|
|
135
|
+
# and the seed is what makes reopening it reproduce this exact image
|
|
136
|
+
if self.workflow_definition is not None:
|
|
137
|
+
metadata["workflow"] = self.workflow_definition
|
|
138
|
+
metadata["seed"] = self.default_seed
|
|
139
|
+
|
|
140
|
+
if "pipeline" in self.step_definition:
|
|
141
|
+
pipeline_def = self.step_definition["pipeline"]
|
|
142
|
+
pretrained_args = pipeline_def.get("from_pretrained_arguments", {})
|
|
143
|
+
if "model_name" in pretrained_args:
|
|
144
|
+
metadata["model_name"] = pretrained_args["model_name"]
|
|
145
|
+
metadata["arguments"] = dict(pipeline_def.get("arguments", {}))
|
|
146
|
+
|
|
147
|
+
elif "task" in self.step_definition:
|
|
148
|
+
task_def = self.step_definition["task"]
|
|
149
|
+
metadata["task_command"] = task_def.get("command", "unknown")
|
|
150
|
+
metadata["arguments"] = dict(task_def.get("arguments", {}))
|
|
151
|
+
|
|
152
|
+
return metadata
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# What json.dumps can embed without falling back to str() - anything else
|
|
156
|
+
# (a PIL image, a tensor, a video's frames) keeps its reference in metadata
|
|
157
|
+
JSON_SCALARS = (str, int, float, bool)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _realize_metadata_arguments(metadata, iterations):
|
|
161
|
+
"""Replace the embedded arguments with the values the step really ran with.
|
|
162
|
+
|
|
163
|
+
Step metadata is collected from the definition, so an argument written as
|
|
164
|
+
"previous_result:expand" is still a reference there - a gallery showing it
|
|
165
|
+
would display the reference instead of the prompt the pipeline saw. The
|
|
166
|
+
iterations hold the resolved values, so lift the JSON-safe ones back into
|
|
167
|
+
the metadata. When iterations disagree (a step fanned out over several
|
|
168
|
+
prior results) every distinct value is kept, in order.
|
|
169
|
+
"""
|
|
170
|
+
arguments = metadata.get("arguments")
|
|
171
|
+
if not arguments or not iterations:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
for key in list(arguments):
|
|
175
|
+
values = []
|
|
176
|
+
for iteration in iterations:
|
|
177
|
+
if not isinstance(iteration, dict) or key not in iteration:
|
|
178
|
+
values = []
|
|
179
|
+
break
|
|
180
|
+
value = iteration[key]
|
|
181
|
+
if not isinstance(value, JSON_SCALARS):
|
|
182
|
+
values = []
|
|
183
|
+
break
|
|
184
|
+
if value not in values:
|
|
185
|
+
values.append(value)
|
|
186
|
+
if values:
|
|
187
|
+
arguments[key] = values[0] if len(values) == 1 else values
|