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/task_domains.py
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"""What a task command's numeric arguments are allowed to be.
|
|
2
|
+
|
|
3
|
+
A task command's arguments are whatever its implementation's signature takes
|
|
4
|
+
(`describe_task` in dw/introspection.py reads them off it), and the signature
|
|
5
|
+
carries no domain: nothing in `slice_audio(num_frames=...)` says a frame count
|
|
6
|
+
cannot be negative. So `validate_workflow` had nothing to check against, and a
|
|
7
|
+
number outside its domain reached the command - where, if the command happened
|
|
8
|
+
to guard it, the run failed on that step, and if it did not, Python's own
|
|
9
|
+
semantics answered instead. `num_frames: -10` became the Python slice
|
|
10
|
+
`audio[0:-13333]` and returned the track minus its last ten frames, reported
|
|
11
|
+
`succeeded` with no warnings (#139); `target_sample_rate: 0` fell through to a
|
|
12
|
+
44100 Hz default and re-headered the samples unresampled, 38% off in duration
|
|
13
|
+
(#140).
|
|
14
|
+
|
|
15
|
+
The domains are declared here, once, and checked in two places for two
|
|
16
|
+
reasons: statically in `validation_errors`, so a literal out-of-domain number
|
|
17
|
+
is a free pre-flight error at the JSON path the author wrote it at rather than
|
|
18
|
+
a spent run; and at run time by the command itself, since a value that arrives
|
|
19
|
+
from a `variable:`, a `previous_result:` or a computation is not a literal and
|
|
20
|
+
this pass can never see it. The static pass therefore only ever refuses what
|
|
21
|
+
the command would refuse anyway - it is the earlier of two answers, not a
|
|
22
|
+
second opinion.
|
|
23
|
+
|
|
24
|
+
Only numbers whose domain is not a judgement call are listed. Whether a level
|
|
25
|
+
in dBFS or a gain is the *right* one for a mix is the command's business, and
|
|
26
|
+
a command that wants to refuse something subtler does it in its own body -
|
|
27
|
+
but a value's documented range is not a judgement call either: grade's
|
|
28
|
+
temperature and tint are only defined from -1.0 to 1.0, and a value outside
|
|
29
|
+
it is not a bolder version of the effect, just an unmodelled one (#349).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
import numbers
|
|
34
|
+
|
|
35
|
+
from .for_each import MEMBER_SEPARATOR, render_path
|
|
36
|
+
|
|
37
|
+
logger = logging.getLogger("dw")
|
|
38
|
+
|
|
39
|
+
# Above zero, and zero-or-above. A count of frames to cut is the first kind -
|
|
40
|
+
# a zero-length slice is not a slice - and an offset to start at is the
|
|
41
|
+
# second, since the head of a track is a legitimate place to begin
|
|
42
|
+
POSITIVE = "positive"
|
|
43
|
+
NON_NEGATIVE = "non_negative"
|
|
44
|
+
# A level that cannot exceed full scale - peak_dbfs's own long-standing rule
|
|
45
|
+
# (0 is full scale, positive is not a level any of these commands can reach),
|
|
46
|
+
# shared here with normalize_audio's target_lufs
|
|
47
|
+
NON_POSITIVE = "non_positive"
|
|
48
|
+
# A value documented as a scale from -1.0 to 1.0 - grade's temperature and
|
|
49
|
+
# tint, whose linear interpolation is only defined inside that range; outside
|
|
50
|
+
# it the same formula still runs and produces a value, just not the one the
|
|
51
|
+
# documented scale promised
|
|
52
|
+
CLOSED_UNIT = "closed_unit"
|
|
53
|
+
|
|
54
|
+
_DOMAIN_TEXT = {
|
|
55
|
+
POSITIVE: "above zero",
|
|
56
|
+
NON_NEGATIVE: "zero or above",
|
|
57
|
+
NON_POSITIVE: "at or below full scale (0)",
|
|
58
|
+
CLOSED_UNIT: "between -1.0 and 1.0",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_DOMAIN_REASON = {
|
|
62
|
+
CLOSED_UNIT: (
|
|
63
|
+
"A value outside that range is refused rather than extrapolated - "
|
|
64
|
+
"the documented scale is only defined inside it"
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
# Shared by POSITIVE, NON_NEGATIVE and NON_POSITIVE, which span both counts/
|
|
68
|
+
# rates (audio, frame) and multipliers (grade's contrast, saturation) - kept
|
|
69
|
+
# neutral rather than naming either, since a wording specific to one reads as
|
|
70
|
+
# nonsense on the other (#383)
|
|
71
|
+
_DEFAULT_REASON = (
|
|
72
|
+
"A value outside that range is refused rather than interpreted - it "
|
|
73
|
+
"would otherwise produce a plausible-looking result outside the "
|
|
74
|
+
"documented range"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# command -> argument -> domain. Every entry here is pinned to a real command
|
|
78
|
+
# and a real parameter of it by tests/test_task_domains.py, so a renamed
|
|
79
|
+
# argument cannot leave a domain checking nothing
|
|
80
|
+
TASK_ARGUMENT_DOMAINS = {
|
|
81
|
+
"slice_audio": {
|
|
82
|
+
"start_seconds": NON_NEGATIVE,
|
|
83
|
+
"duration_seconds": POSITIVE,
|
|
84
|
+
"start_frame": NON_NEGATIVE,
|
|
85
|
+
"num_frames": POSITIVE,
|
|
86
|
+
"fps": POSITIVE,
|
|
87
|
+
"sample_rate": POSITIVE,
|
|
88
|
+
},
|
|
89
|
+
"gain_audio": {
|
|
90
|
+
"start_seconds": NON_NEGATIVE,
|
|
91
|
+
"duration_seconds": POSITIVE,
|
|
92
|
+
"start_frame": NON_NEGATIVE,
|
|
93
|
+
"num_frames": POSITIVE,
|
|
94
|
+
"fps": POSITIVE,
|
|
95
|
+
"sample_rate": POSITIVE,
|
|
96
|
+
},
|
|
97
|
+
"resample_audio": {
|
|
98
|
+
"target_sample_rate": POSITIVE,
|
|
99
|
+
"sample_rate": POSITIVE,
|
|
100
|
+
},
|
|
101
|
+
"loop_audio": {
|
|
102
|
+
"duration_seconds": POSITIVE,
|
|
103
|
+
"target_frames": POSITIVE,
|
|
104
|
+
"fps": POSITIVE,
|
|
105
|
+
"crossfade_ms": NON_NEGATIVE,
|
|
106
|
+
"sample_rate": POSITIVE,
|
|
107
|
+
},
|
|
108
|
+
"fade_audio": {
|
|
109
|
+
"fade_in_ms": NON_NEGATIVE,
|
|
110
|
+
"fade_out_ms": NON_NEGATIVE,
|
|
111
|
+
"sample_rate": POSITIVE,
|
|
112
|
+
},
|
|
113
|
+
"normalize_audio": {
|
|
114
|
+
"sample_rate": POSITIVE,
|
|
115
|
+
"target_lufs": NON_POSITIVE,
|
|
116
|
+
},
|
|
117
|
+
"crossfade_audio": {
|
|
118
|
+
"crossfade_ms": NON_NEGATIVE,
|
|
119
|
+
"sample_rate": POSITIVE,
|
|
120
|
+
},
|
|
121
|
+
"mix_audio": {"gains": NON_NEGATIVE, "sample_rate": POSITIVE},
|
|
122
|
+
"pair_audio": {"sample_rate": POSITIVE},
|
|
123
|
+
"concat_videos": {
|
|
124
|
+
"trim_frames": NON_NEGATIVE,
|
|
125
|
+
"crossfade_ms": NON_NEGATIVE,
|
|
126
|
+
"audio_bleed_ms": NON_NEGATIVE,
|
|
127
|
+
"seam_fade_ms": NON_NEGATIVE,
|
|
128
|
+
"fps": POSITIVE,
|
|
129
|
+
"sample_rate": POSITIVE,
|
|
130
|
+
},
|
|
131
|
+
"loop_frames": {"num_frames": POSITIVE},
|
|
132
|
+
"frame_grid": {"count": POSITIVE, "columns": POSITIVE, "tile_width": POSITIVE},
|
|
133
|
+
"dissolve_videos": {
|
|
134
|
+
"dissolve_frames": NON_NEGATIVE,
|
|
135
|
+
"fade_in_frames": NON_NEGATIVE,
|
|
136
|
+
"fade_out_frames": NON_NEGATIVE,
|
|
137
|
+
"fps": POSITIVE,
|
|
138
|
+
},
|
|
139
|
+
"compress_audio": {
|
|
140
|
+
"ratio": POSITIVE,
|
|
141
|
+
"attack_ms": NON_NEGATIVE,
|
|
142
|
+
"release_ms": NON_NEGATIVE,
|
|
143
|
+
"sample_rate": POSITIVE,
|
|
144
|
+
},
|
|
145
|
+
"filter_audio": {
|
|
146
|
+
"cutoff_hz": POSITIVE,
|
|
147
|
+
"sample_rate": POSITIVE,
|
|
148
|
+
},
|
|
149
|
+
"analyze_audio": {"sample_rate": POSITIVE},
|
|
150
|
+
"grade": {
|
|
151
|
+
"contrast": NON_NEGATIVE,
|
|
152
|
+
"saturation": NON_NEGATIVE,
|
|
153
|
+
"temperature": CLOSED_UNIT,
|
|
154
|
+
"tint": CLOSED_UNIT,
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def in_domain(value, domain):
|
|
160
|
+
"""Whether a number satisfies a domain. Anything unmeasurable is True -
|
|
161
|
+
a value this cannot read is not this check's to refuse."""
|
|
162
|
+
number = as_number(value)
|
|
163
|
+
if number is None:
|
|
164
|
+
return True
|
|
165
|
+
if domain == POSITIVE:
|
|
166
|
+
return number > 0
|
|
167
|
+
if domain == NON_POSITIVE:
|
|
168
|
+
return number <= 0
|
|
169
|
+
if domain == CLOSED_UNIT:
|
|
170
|
+
return -1.0 <= number <= 1.0
|
|
171
|
+
return number >= 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def as_number(value):
|
|
175
|
+
"""A value as a float if it is one, else None.
|
|
176
|
+
|
|
177
|
+
A workflow variable declared null carries no type, so a number supplied
|
|
178
|
+
for it on the command line arrives as a string - the tasks coerce those
|
|
179
|
+
(`_as_number` in audio_utils), so this reads them too. Booleans are not
|
|
180
|
+
numbers here whatever Python thinks, and a `variable:`/`item:`/
|
|
181
|
+
`previous_result:` string is somebody else's complaint.
|
|
182
|
+
"""
|
|
183
|
+
if isinstance(value, bool) or value is None:
|
|
184
|
+
return None
|
|
185
|
+
# numbers.Real rather than (int, float): an fps is coerced to a Fraction
|
|
186
|
+
# before it is checked, so an exact 24000/1001 stays exact
|
|
187
|
+
if isinstance(value, numbers.Real):
|
|
188
|
+
return float(value)
|
|
189
|
+
if isinstance(value, str):
|
|
190
|
+
try:
|
|
191
|
+
return float(value)
|
|
192
|
+
except ValueError:
|
|
193
|
+
return None
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _domain_candidates(value):
|
|
198
|
+
"""(index, item) pairs to check - one per element of a list argument
|
|
199
|
+
(mix_audio's 'gains', one multiplier per track), else the value itself
|
|
200
|
+
paired with no index."""
|
|
201
|
+
if isinstance(value, list):
|
|
202
|
+
return list(enumerate(value))
|
|
203
|
+
return [(None, value)]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def domain_violation(command, name, value, domain):
|
|
207
|
+
"""(index, message) for the first out-of-domain element, or None if all
|
|
208
|
+
are fine. index is None when the argument itself is the scalar checked
|
|
209
|
+
rather than one entry of a list.
|
|
210
|
+
|
|
211
|
+
Shared by the static pass and the commands' own run-time guards so the
|
|
212
|
+
two cannot word the same refusal differently.
|
|
213
|
+
"""
|
|
214
|
+
for index, item in _domain_candidates(value):
|
|
215
|
+
if in_domain(item, domain):
|
|
216
|
+
continue
|
|
217
|
+
label = f"{name}[{index}]" if index is not None else name
|
|
218
|
+
reason = _DOMAIN_REASON.get(domain, _DEFAULT_REASON)
|
|
219
|
+
message = (
|
|
220
|
+
f"{command} needs '{label}' {_DOMAIN_TEXT[domain]}, got {item!r}. {reason}"
|
|
221
|
+
)
|
|
222
|
+
return index, message
|
|
223
|
+
return None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def domain_error(command, name, value, domain):
|
|
227
|
+
"""The message for one out-of-domain argument, or None if it is fine."""
|
|
228
|
+
violation = domain_violation(command, name, value, domain)
|
|
229
|
+
return None if violation is None else violation[1]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def check_argument(command, name, value):
|
|
233
|
+
"""Raise ValueError if a task argument is outside its declared domain.
|
|
234
|
+
|
|
235
|
+
What a command calls on the value it was actually handed, after its own
|
|
236
|
+
coercion - the value may have come from a variable or an earlier step,
|
|
237
|
+
which the static pass never sees.
|
|
238
|
+
"""
|
|
239
|
+
domain = TASK_ARGUMENT_DOMAINS.get(command, {}).get(name)
|
|
240
|
+
if domain is None:
|
|
241
|
+
return
|
|
242
|
+
message = domain_error(command, name, value, domain)
|
|
243
|
+
if message is not None:
|
|
244
|
+
raise ValueError(message)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def check_arguments(command, **values):
|
|
248
|
+
"""check_argument over several of a command's arguments, in order."""
|
|
249
|
+
for name, value in values.items():
|
|
250
|
+
check_argument(command, name, value)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def task_argument_errors(workflow_definition, source_indices=None):
|
|
254
|
+
"""Every task argument outside its declared domain, as [{path, message}].
|
|
255
|
+
|
|
256
|
+
The definition handed here has already been substituted and expanded, so
|
|
257
|
+
a `for_each` member's own arguments are checked as they will run;
|
|
258
|
+
`source_indices` maps each expanded step back to the step the author
|
|
259
|
+
wrote, and the member is named in the message - the convention
|
|
260
|
+
subfolder_errors and reference_limit_errors both follow.
|
|
261
|
+
"""
|
|
262
|
+
steps = workflow_definition.get("steps")
|
|
263
|
+
if not isinstance(steps, list):
|
|
264
|
+
return []
|
|
265
|
+
|
|
266
|
+
errors = []
|
|
267
|
+
for index, step in enumerate(steps):
|
|
268
|
+
if not isinstance(step, dict):
|
|
269
|
+
continue
|
|
270
|
+
task = step.get("task")
|
|
271
|
+
if not isinstance(task, dict):
|
|
272
|
+
continue
|
|
273
|
+
command = task.get("command")
|
|
274
|
+
arguments = task.get("arguments")
|
|
275
|
+
if not isinstance(command, str) or not isinstance(arguments, dict):
|
|
276
|
+
continue
|
|
277
|
+
domains = TASK_ARGUMENT_DOMAINS.get(command)
|
|
278
|
+
if not domains:
|
|
279
|
+
continue
|
|
280
|
+
source = (
|
|
281
|
+
source_indices[index]
|
|
282
|
+
if source_indices is not None and index < len(source_indices)
|
|
283
|
+
else index
|
|
284
|
+
)
|
|
285
|
+
name = step.get("name")
|
|
286
|
+
where = (
|
|
287
|
+
f" in member '{name}'"
|
|
288
|
+
if isinstance(name, str) and MEMBER_SEPARATOR in name
|
|
289
|
+
else ""
|
|
290
|
+
)
|
|
291
|
+
for key, domain in domains.items():
|
|
292
|
+
if key not in arguments:
|
|
293
|
+
continue
|
|
294
|
+
violation = domain_violation(command, key, arguments[key], domain)
|
|
295
|
+
if violation is None:
|
|
296
|
+
continue
|
|
297
|
+
element_index, message = violation
|
|
298
|
+
path = ("steps", source, "task", "arguments", key)
|
|
299
|
+
if element_index is not None:
|
|
300
|
+
path = path + (element_index,)
|
|
301
|
+
errors.append(
|
|
302
|
+
{
|
|
303
|
+
"path": render_path(path),
|
|
304
|
+
"message": f"{message}{where}.",
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
return errors
|