lcode-cli 0.44.0__py3-none-any.whl → 0.45.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.
- lcode/__init__.py +1 -1
- lcode/imageopts.py +29 -1
- lcode/media.py +66 -16
- lcode/media.toml +50 -0
- lcode/media_catalog.py +4 -0
- lcode/repl.py +13 -2
- lcode/workflows/wan21-fun-camera.json +186 -0
- lcode/workflows/wan22-fun-inp-5b.json +159 -0
- {lcode_cli-0.44.0.dist-info → lcode_cli-0.45.0.dist-info}/METADATA +1 -1
- {lcode_cli-0.44.0.dist-info → lcode_cli-0.45.0.dist-info}/RECORD +13 -11
- {lcode_cli-0.44.0.dist-info → lcode_cli-0.45.0.dist-info}/WHEEL +0 -0
- {lcode_cli-0.44.0.dist-info → lcode_cli-0.45.0.dist-info}/entry_points.txt +0 -0
- {lcode_cli-0.44.0.dist-info → lcode_cli-0.45.0.dist-info}/licenses/LICENSE +0 -0
lcode/__init__.py
CHANGED
lcode/imageopts.py
CHANGED
|
@@ -35,6 +35,21 @@ STYLES = {
|
|
|
35
35
|
"comic": "comic book art, bold ink outlines, halftone shading",
|
|
36
36
|
"flat": "flat vector illustration, minimal, bold shapes",
|
|
37
37
|
}
|
|
38
|
+
CAMERA = { # video: --camera name -> the camera model's pose
|
|
39
|
+
"static": "Static",
|
|
40
|
+
"pan-left": "Pan Left",
|
|
41
|
+
"pan-right": "Pan Right",
|
|
42
|
+
"pan-up": "Pan Up",
|
|
43
|
+
"pan-down": "Pan Down",
|
|
44
|
+
"tilt-up": "Pan Up",
|
|
45
|
+
"tilt-down": "Pan Down",
|
|
46
|
+
"zoom-in": "Zoom In",
|
|
47
|
+
"zoom-out": "Zoom Out",
|
|
48
|
+
"dolly-in": "Zoom In",
|
|
49
|
+
"dolly-out": "Zoom Out",
|
|
50
|
+
"rotate-left": "Anti Clockwise (ACW)",
|
|
51
|
+
"rotate-right": "ClockWise (CW)",
|
|
52
|
+
}
|
|
38
53
|
ALIASES = {
|
|
39
54
|
"n": "n",
|
|
40
55
|
"count": "n",
|
|
@@ -49,6 +64,7 @@ ALIASES = {
|
|
|
49
64
|
"style": "style",
|
|
50
65
|
"seconds": "seconds",
|
|
51
66
|
"duration": "seconds",
|
|
67
|
+
"camera": "camera",
|
|
52
68
|
}
|
|
53
69
|
FLAG = re.compile(r"(?:^|\s)--?(" + "|".join(sorted(ALIASES, key=len, reverse=True)) + r")\b(?=[\s=]|$)")
|
|
54
70
|
DIMENSIONS = re.compile(r"^(\d{2,4})\s*[x×]\s*(\d{2,4})$")
|
|
@@ -68,12 +84,15 @@ class Options:
|
|
|
68
84
|
negative: str = ""
|
|
69
85
|
style: str = ""
|
|
70
86
|
seconds: float = 5.0 # video only
|
|
87
|
+
camera: str = "" # video only: a move from CAMERA, made by a camera-control model
|
|
71
88
|
|
|
72
89
|
def describe(self, video: bool = False) -> str:
|
|
73
90
|
seed = "random" if self.seed is None else str(self.seed)
|
|
74
91
|
parts = [f"n={self.n}", f"size={self.size}", f"seed={seed}", f"quality={self.quality}"]
|
|
75
92
|
if video:
|
|
76
93
|
parts.append(f"seconds={self.seconds:g}")
|
|
94
|
+
if self.camera:
|
|
95
|
+
parts.append(f"camera={self.camera}")
|
|
77
96
|
if self.style:
|
|
78
97
|
parts.append(f"style={self.style}")
|
|
79
98
|
if self.negative:
|
|
@@ -98,7 +117,9 @@ def set_values(options: Options, values: dict[str, object]) -> Options:
|
|
|
98
117
|
for raw_key, raw in values.items():
|
|
99
118
|
key = ALIASES.get(str(raw_key).lower().lstrip("-"))
|
|
100
119
|
if key is None:
|
|
101
|
-
raise OptionError(
|
|
120
|
+
raise OptionError(
|
|
121
|
+
f"unknown option {raw_key!r}: use n, ar (or size), seed, quality, style, no, seconds or camera"
|
|
122
|
+
)
|
|
102
123
|
value = str(raw).strip() if raw is not None else ""
|
|
103
124
|
if key == "n":
|
|
104
125
|
if not value.isdigit() or not 1 <= int(value) <= MAX_COUNT:
|
|
@@ -126,6 +147,13 @@ def set_values(options: Options, values: dict[str, object]) -> Options:
|
|
|
126
147
|
raise OptionError(f"style is one of {', '.join(STYLES)} (or none)")
|
|
127
148
|
elif key == "negative":
|
|
128
149
|
out.negative = "" if value.lower() in ("none", "off") else value
|
|
150
|
+
elif key == "camera":
|
|
151
|
+
if value.lower() in ("none", "off", ""):
|
|
152
|
+
out.camera = ""
|
|
153
|
+
elif value.lower() in CAMERA:
|
|
154
|
+
out.camera = value.lower()
|
|
155
|
+
else:
|
|
156
|
+
raise OptionError(f"camera is one of {', '.join(CAMERA)} (or none)")
|
|
129
157
|
elif key == "seconds":
|
|
130
158
|
try:
|
|
131
159
|
seconds = float(value.rstrip("s"))
|
lcode/media.py
CHANGED
|
@@ -292,7 +292,7 @@ def request(
|
|
|
292
292
|
if agent.settings.mode == "video":
|
|
293
293
|
if mask:
|
|
294
294
|
raise MediaError("Video mode doesn't take a mask: @photo.png starts a video from a picture.")
|
|
295
|
-
job = {"kind": "video", "prompt": prompt, "options": options, "sources": [str(p) for p in sources[:
|
|
295
|
+
job = {"kind": "video", "prompt": prompt, "options": options, "sources": [str(p) for p in sources[:2]],
|
|
296
296
|
"mask": None, "pads": None, "keep_shape": not SIZE_FLAG.search(text)} # fmt: skip
|
|
297
297
|
return redo(agent, job, cancelled, ask)
|
|
298
298
|
job = {"kind": "edit" if sources else "image", "prompt": prompt, "options": options,
|
|
@@ -488,10 +488,14 @@ def generate_video(
|
|
|
488
488
|
image (the shapes video models know best; the image is cropped to it), unless the request gives one."""
|
|
489
489
|
prompt = job["prompt"].strip()
|
|
490
490
|
server = prepare(agent, ask, cancelled)
|
|
491
|
-
|
|
491
|
+
sources = [Path(p) for p in job.get("sources") or []]
|
|
492
|
+
start = sources[0] if sources else None
|
|
493
|
+
end = sources[1] if len(sources) > 1 else None
|
|
494
|
+
clip = Path(job["extend"]) if job.get("extend") else None
|
|
495
|
+
model = video_model(agent, "first-last-frame" if end else ("camera" if options.camera else
|
|
496
|
+
("image-to-video" if clip else ""))) # fmt: skip
|
|
492
497
|
files = ensure_installed(agent, model, server, ask)
|
|
493
498
|
workflow = comfy.load_workflow(model.workflow)
|
|
494
|
-
start = Path(job["sources"][0]) if job.get("sources") else None
|
|
495
499
|
seed = options.seed if options.seed is not None else random.randrange(2**32)
|
|
496
500
|
steps, megapixels = model.preset(options.quality)
|
|
497
501
|
ratio = options.size
|
|
@@ -504,6 +508,8 @@ def generate_video(
|
|
|
504
508
|
except (OSError, edits.EditError):
|
|
505
509
|
pass
|
|
506
510
|
width, height = imageopts.dimensions(imageopts.SIZES.get(ratio, ratio), megapixels, multiple=32)
|
|
511
|
+
if clip is not None: # the new part continues the clip, so it has its size
|
|
512
|
+
width, height = job["size"]
|
|
507
513
|
fps = int(workflow.defaults.get("fps", 24))
|
|
508
514
|
text = imageopts.styled(prompt, options.style)
|
|
509
515
|
values = {**workflow.defaults, **files, "prompt": text, "seed": seed, "width": width, "height": height,
|
|
@@ -512,26 +518,70 @@ def generate_video(
|
|
|
512
518
|
values["steps"] = steps
|
|
513
519
|
if options.negative:
|
|
514
520
|
values["negative"] = options.negative
|
|
521
|
+
values["camera"] = imageopts.CAMERA[options.camera or "static"]
|
|
515
522
|
graph = comfy.fill(workflow.graph, values)
|
|
516
|
-
if
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
523
|
+
latent = next((edits.node_of(graph, c) for c in VIDEO_LATENTS if edits.node_of(graph, c)), None)
|
|
524
|
+
try:
|
|
525
|
+
if start is not None:
|
|
526
|
+
graph["8100"] = {"class_type": "LoadImage", "inputs": {"image": server.upload(start)}}
|
|
527
|
+
graph[latent]["inputs"]["start_image"] = ["8100", 0]
|
|
528
|
+
loader = edits.node_of(graph, "CLIPVisionLoader")
|
|
529
|
+
if loader is not None: # the camera model also looks at the start image
|
|
530
|
+
look = {"clip_vision": [loader, 0], "image": ["8100", 0], "crop": "none"}
|
|
531
|
+
graph["8102"] = {"class_type": "CLIPVisionEncode", "inputs": look}
|
|
532
|
+
graph[latent]["inputs"]["clip_vision_output"] = ["8102", 0]
|
|
533
|
+
if end is not None:
|
|
534
|
+
graph["8101"] = {"class_type": "LoadImage", "inputs": {"image": server.upload(end)}}
|
|
535
|
+
graph[latent]["inputs"]["end_image"] = ["8101", 0]
|
|
536
|
+
if clip is not None:
|
|
537
|
+
continue_clip(graph, latent, server.upload(clip), values["length"])
|
|
538
|
+
except (comfy.ComfyError, OSError) as e:
|
|
539
|
+
raise MediaError(f"Couldn't hand the files to ComfyUI: {e}") from e
|
|
526
540
|
add_thumbnail(graph, width, height)
|
|
527
541
|
meta = describe_job(model, workflow, prompt, text, seed, values, options, files)
|
|
528
542
|
meta.update({"seconds": round((values["length"] - 1) / fps, 2), "fps": fps, "frames": values["length"],
|
|
529
|
-
"start_image": shown(agent, start) if start else ""
|
|
530
|
-
|
|
531
|
-
|
|
543
|
+
"start_image": shown(agent, start) if start else "", "end_image": shown(agent, end) if end else "",
|
|
544
|
+
"camera": options.camera, "extends": shown(agent, clip) if clip else ""}) # fmt: skip
|
|
545
|
+
remembered = {k: job.get(k) for k in ("kind", "prompt", "sources", "mask", "pads", "keep_shape", "extend", "size")}
|
|
546
|
+
stem = f"{clip.stem}-extended" if clip else ""
|
|
547
|
+
plan = Plan(model, workflow, graph, meta, prompt, seed, options, "video", job.get("sources") or [], remembered,
|
|
548
|
+
stem) # fmt: skip
|
|
532
549
|
return run_plan(agent, server, plan, cancelled, label)
|
|
533
550
|
|
|
534
551
|
|
|
552
|
+
VIDEO_LATENTS = ("Wan22ImageToVideoLatent", "WanFunInpaintToVideo", "WanCameraImageToVideo")
|
|
553
|
+
WHY = {
|
|
554
|
+
"first-last-frame": "a clip between two frames",
|
|
555
|
+
"camera": "a camera move",
|
|
556
|
+
"image-to-video": "continuing a clip",
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def video_model(agent: Agent, task: str) -> catalog.MediaModel:
|
|
561
|
+
"""The video model for a job: the chosen one, or the first that does what the job needs."""
|
|
562
|
+
chosen = current(agent, "video")
|
|
563
|
+
if not task or task in chosen.tasks:
|
|
564
|
+
return chosen
|
|
565
|
+
model = catalog.load().for_task("video", task)
|
|
566
|
+
if model is None:
|
|
567
|
+
raise MediaError(f"None of lcode's video models does {WHY.get(task, task)} yet.")
|
|
568
|
+
agent.say(f"{model.name} makes this one ({WHY.get(task, task)}).")
|
|
569
|
+
return model
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def continue_clip(graph: dict, latent: str, clip_name: str, length: int) -> None:
|
|
573
|
+
"""Extending a clip: start from its last frame, and join the new frames (without that first one) after it."""
|
|
574
|
+
graph["8200"] = {"class_type": "LoadVideo", "inputs": {"file": clip_name}}
|
|
575
|
+
graph["8201"] = {"class_type": "GetVideoComponents", "inputs": {"video": ["8200", 0]}}
|
|
576
|
+
graph["8202"] = {"class_type": "ImageFromBatch", "inputs": {"image": ["8201", 0], "batch_index": -1, "length": 1}}
|
|
577
|
+
graph[latent]["inputs"]["start_image"] = ["8202", 0]
|
|
578
|
+
video = edits.node_of(graph, "CreateVideo")
|
|
579
|
+
decoded = graph[video]["inputs"]["images"]
|
|
580
|
+
graph["8203"] = {"class_type": "ImageFromBatch", "inputs": {"image": decoded, "batch_index": 1, "length": length}}
|
|
581
|
+
graph["8204"] = {"class_type": "ImageBatch", "inputs": {"image1": ["8201", 0], "image2": ["8203", 0]}}
|
|
582
|
+
graph[video]["inputs"]["images"] = ["8204", 0]
|
|
583
|
+
|
|
584
|
+
|
|
535
585
|
def apply_tool(
|
|
536
586
|
agent: Agent,
|
|
537
587
|
task: str,
|
lcode/media.toml
CHANGED
|
@@ -146,3 +146,53 @@ measured = "5 s clip: 100 s fast, 150 s balanced (864×480), 10.5 min best (1280
|
|
|
146
146
|
notes = "5 s at 24 fps, up to 1280×704; text or a start image"
|
|
147
147
|
quality = { fast = [12, 0.4], balanced = [20, 0.4], best = [30, 0.859] }
|
|
148
148
|
negative = true
|
|
149
|
+
|
|
150
|
+
[files.wan22-fun-inp-5b]
|
|
151
|
+
folder = "diffusion_models"
|
|
152
|
+
name = "wan2.2_fun_inpaint_5B_bf16.safetensors"
|
|
153
|
+
url = "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_fun_inpaint_5B_bf16.safetensors"
|
|
154
|
+
gb = 10.00
|
|
155
|
+
|
|
156
|
+
[files.wan21-fun-camera-1-3b]
|
|
157
|
+
folder = "diffusion_models"
|
|
158
|
+
name = "wan2.1_fun_camera_v1.1_1.3B_bf16.safetensors"
|
|
159
|
+
url = "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_fun_camera_v1.1_1.3B_bf16.safetensors"
|
|
160
|
+
gb = 3.23
|
|
161
|
+
|
|
162
|
+
[files.clip-vision-h]
|
|
163
|
+
folder = "clip_vision"
|
|
164
|
+
name = "clip_vision_h.safetensors"
|
|
165
|
+
url = "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors"
|
|
166
|
+
gb = 1.26
|
|
167
|
+
|
|
168
|
+
[files.wan21-vae]
|
|
169
|
+
folder = "vae"
|
|
170
|
+
name = "wan_2.1_vae.safetensors"
|
|
171
|
+
url = "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors"
|
|
172
|
+
gb = 0.25
|
|
173
|
+
|
|
174
|
+
[models.wan22-fun-inp-5b]
|
|
175
|
+
name = "Wan 2.2 Fun InP 5B"
|
|
176
|
+
mode = "video"
|
|
177
|
+
publisher = "Alibaba PAI"
|
|
178
|
+
license = "Apache 2.0"
|
|
179
|
+
tasks = ["first-last-frame"]
|
|
180
|
+
workflow = "wan22-fun-inp-5b"
|
|
181
|
+
files = { model = "wan22-fun-inp-5b", text_encoder = "umt5-xxl-fp8", vae = "wan22-vae" }
|
|
182
|
+
measured = "3 s clip, fast: 62 s (864×480) on an RTX 4080 Laptop, 12 GB"
|
|
183
|
+
notes = "a clip from a first frame to a last frame: @start.png @end.png"
|
|
184
|
+
quality = { fast = [12, 0.4], balanced = [20, 0.4], best = [30, 0.859] }
|
|
185
|
+
negative = true
|
|
186
|
+
|
|
187
|
+
[models.wan21-fun-camera-1-3b]
|
|
188
|
+
name = "Wan 2.1 Fun Camera 1.3B"
|
|
189
|
+
mode = "video"
|
|
190
|
+
publisher = "Alibaba PAI"
|
|
191
|
+
license = "Apache 2.0"
|
|
192
|
+
tasks = ["camera"]
|
|
193
|
+
workflow = "wan21-fun-camera"
|
|
194
|
+
files = { model = "wan21-fun-camera-1-3b", text_encoder = "umt5-xxl-fp8", vae = "wan21-vae", clip_vision = "clip-vision-h" }
|
|
195
|
+
measured = "3 s clip (49 frames, 832×480): 115 s on an RTX 4080 Laptop, 12 GB"
|
|
196
|
+
notes = "camera moves (--camera pan-left, zoom-in, …), small and fast, 16 fps"
|
|
197
|
+
quality = { fast = [12, 0.38], balanced = [20, 0.38], best = [30, 0.38] }
|
|
198
|
+
negative = true
|
lcode/media_catalog.py
CHANGED
|
@@ -112,6 +112,10 @@ class Catalog:
|
|
|
112
112
|
def for_mode(self, mode: str) -> list[MediaModel]:
|
|
113
113
|
return [m for m in self.models.values() if m.mode == mode]
|
|
114
114
|
|
|
115
|
+
def for_task(self, mode: str, task: str) -> MediaModel | None:
|
|
116
|
+
"""The first model of a mode that does a task (first-last-frame, camera, image-to-video…)."""
|
|
117
|
+
return next((m for m in self.for_mode(mode) if task in m.tasks), None)
|
|
118
|
+
|
|
115
119
|
def tool(self, task: str) -> MediaModel:
|
|
116
120
|
"""The tool for a task (upscale, remove-background)."""
|
|
117
121
|
return next(m for m in self.models.values() if m.mode == "tool" and task in m.tasks)
|
lcode/repl.py
CHANGED
|
@@ -86,7 +86,7 @@ COMMANDS = {
|
|
|
86
86
|
"/open": "Open an image in your viewer, e.g. /open 3 (default: the last)",
|
|
87
87
|
"/edit": "Image mode: change an image by instruction, e.g. /edit 3 make the sky purple (@photo.png: references)",
|
|
88
88
|
"/use": "Image mode: images the next requests follow, e.g. /use 3 or /use photo.png (/use off)",
|
|
89
|
-
"/extend": "
|
|
89
|
+
"/extend": "Outpaint an image (/extend 3 --ar 16:9), or continue a clip in video mode (/extend 4 --seconds 3)",
|
|
90
90
|
"/upscale": "Image mode: 2 or 4 times the pixels, e.g. /upscale 3 4",
|
|
91
91
|
"/nobg": "Image mode: remove the background (a transparent PNG), e.g. /nobg 3",
|
|
92
92
|
"/animate": "Image mode: make a video starting from an image, e.g. /animate 3 the waves roll in (video mode)",
|
|
@@ -1406,7 +1406,7 @@ IMAGE_COMMANDS = (
|
|
|
1406
1406
|
"/options", "/again", "/vary", "/gallery", "/open", "/play", "/edit", "/use", "/extend", "/upscale", "/nobg",
|
|
1407
1407
|
"/animate",
|
|
1408
1408
|
) # fmt: skip
|
|
1409
|
-
VIDEO_TOO = ("/options", "/again", "/vary", "/gallery", "/open", "/play", "/use") #
|
|
1409
|
+
VIDEO_TOO = ("/options", "/again", "/vary", "/gallery", "/open", "/play", "/use", "/extend") # video mode has these
|
|
1410
1410
|
NUMBER = re.compile(r"^#?(\d+)$")
|
|
1411
1411
|
|
|
1412
1412
|
|
|
@@ -1495,6 +1495,17 @@ def image_command(agent: Agent, cmd: str, arg: str) -> None:
|
|
|
1495
1495
|
"pads": None, "keep_shape": "--ar" not in rest and "--size" not in rest} # fmt: skip
|
|
1496
1496
|
make_image(agent, run=lambda cancel, ask: media.redo(agent, job, cancel, ask))
|
|
1497
1497
|
return
|
|
1498
|
+
if cmd == "/extend" and video:
|
|
1499
|
+
if edits.is_image(source):
|
|
1500
|
+
c.print(f"[yellow]#{entry['n']} is a picture: /animate it, or /extend a clip.[/]")
|
|
1501
|
+
return
|
|
1502
|
+
text, options = imageopts.parse(rest, imageopts.replace_seed(agent.image_options, n=1))
|
|
1503
|
+
job = {"kind": "video", "prompt": text or entry["prompt"], "options": options, "sources": [],
|
|
1504
|
+
"mask": None, "pads": None, "keep_shape": True, "extend": str(source),
|
|
1505
|
+
"size": [entry["width"], entry["height"]]} # fmt: skip
|
|
1506
|
+
c.print(f"[dim]Continuing #{entry['n']} from its last frame, {options.seconds:g} s more.[/]")
|
|
1507
|
+
make_image(agent, run=lambda cancel, ask: media.redo(agent, job, cancel, ask))
|
|
1508
|
+
return
|
|
1498
1509
|
if cmd == "/extend":
|
|
1499
1510
|
width, height = edits.image_size(source)
|
|
1500
1511
|
pads, text = edits.parse_extend(rest, width, height)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "ComfyUI's template video_wan2.1_fun_camera_v1.1_1.3B, in API format (lcode adds a start image when given)",
|
|
3
|
+
"defaults": {
|
|
4
|
+
"width": 832,
|
|
5
|
+
"height": 480,
|
|
6
|
+
"length": 81,
|
|
7
|
+
"fps": 16,
|
|
8
|
+
"steps": 20,
|
|
9
|
+
"negative": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
|
|
10
|
+
"camera": "Static",
|
|
11
|
+
"speed": 1.0
|
|
12
|
+
},
|
|
13
|
+
"labels": {
|
|
14
|
+
"1": "loading the model",
|
|
15
|
+
"2": "loading the text encoder",
|
|
16
|
+
"3": "loading the VAE",
|
|
17
|
+
"5": "reading the prompt",
|
|
18
|
+
"6": "reading the prompt",
|
|
19
|
+
"7": "planning the camera move",
|
|
20
|
+
"8": "preparing the frames",
|
|
21
|
+
"9": "drawing the frames",
|
|
22
|
+
"10": "decoding the frames",
|
|
23
|
+
"11": "making the video",
|
|
24
|
+
"12": "saving"
|
|
25
|
+
},
|
|
26
|
+
"graph": {
|
|
27
|
+
"1": {
|
|
28
|
+
"class_type": "UNETLoader",
|
|
29
|
+
"inputs": {
|
|
30
|
+
"unet_name": "$model",
|
|
31
|
+
"weight_dtype": "default"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"2": {
|
|
35
|
+
"class_type": "CLIPLoader",
|
|
36
|
+
"inputs": {
|
|
37
|
+
"clip_name": "$text_encoder",
|
|
38
|
+
"type": "wan",
|
|
39
|
+
"device": "default"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"3": {
|
|
43
|
+
"class_type": "VAELoader",
|
|
44
|
+
"inputs": {
|
|
45
|
+
"vae_name": "$vae"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"4": {
|
|
49
|
+
"class_type": "ModelSamplingSD3",
|
|
50
|
+
"inputs": {
|
|
51
|
+
"model": [
|
|
52
|
+
"1",
|
|
53
|
+
0
|
|
54
|
+
],
|
|
55
|
+
"shift": 8
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"5": {
|
|
59
|
+
"class_type": "CLIPTextEncode",
|
|
60
|
+
"inputs": {
|
|
61
|
+
"text": "$prompt",
|
|
62
|
+
"clip": [
|
|
63
|
+
"2",
|
|
64
|
+
0
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"6": {
|
|
69
|
+
"class_type": "CLIPTextEncode",
|
|
70
|
+
"inputs": {
|
|
71
|
+
"text": "$negative",
|
|
72
|
+
"clip": [
|
|
73
|
+
"2",
|
|
74
|
+
0
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"7": {
|
|
79
|
+
"class_type": "WanCameraEmbedding",
|
|
80
|
+
"inputs": {
|
|
81
|
+
"camera_pose": "$camera",
|
|
82
|
+
"width": "$width",
|
|
83
|
+
"height": "$height",
|
|
84
|
+
"length": "$length",
|
|
85
|
+
"speed": "$speed",
|
|
86
|
+
"fx": 0.5,
|
|
87
|
+
"fy": 0.5,
|
|
88
|
+
"cx": 0.5,
|
|
89
|
+
"cy": 0.5
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"8": {
|
|
93
|
+
"class_type": "WanCameraImageToVideo",
|
|
94
|
+
"inputs": {
|
|
95
|
+
"positive": [
|
|
96
|
+
"5",
|
|
97
|
+
0
|
|
98
|
+
],
|
|
99
|
+
"negative": [
|
|
100
|
+
"6",
|
|
101
|
+
0
|
|
102
|
+
],
|
|
103
|
+
"vae": [
|
|
104
|
+
"3",
|
|
105
|
+
0
|
|
106
|
+
],
|
|
107
|
+
"width": "$width",
|
|
108
|
+
"height": "$height",
|
|
109
|
+
"length": "$length",
|
|
110
|
+
"batch_size": 1,
|
|
111
|
+
"camera_conditions": [
|
|
112
|
+
"7",
|
|
113
|
+
0
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"9": {
|
|
118
|
+
"class_type": "KSampler",
|
|
119
|
+
"inputs": {
|
|
120
|
+
"model": [
|
|
121
|
+
"4",
|
|
122
|
+
0
|
|
123
|
+
],
|
|
124
|
+
"seed": "$seed",
|
|
125
|
+
"steps": "$steps",
|
|
126
|
+
"cfg": 6,
|
|
127
|
+
"sampler_name": "uni_pc",
|
|
128
|
+
"scheduler": "simple",
|
|
129
|
+
"positive": [
|
|
130
|
+
"8",
|
|
131
|
+
0
|
|
132
|
+
],
|
|
133
|
+
"negative": [
|
|
134
|
+
"8",
|
|
135
|
+
1
|
|
136
|
+
],
|
|
137
|
+
"latent_image": [
|
|
138
|
+
"8",
|
|
139
|
+
2
|
|
140
|
+
],
|
|
141
|
+
"denoise": 1
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
"10": {
|
|
145
|
+
"class_type": "VAEDecode",
|
|
146
|
+
"inputs": {
|
|
147
|
+
"samples": [
|
|
148
|
+
"9",
|
|
149
|
+
0
|
|
150
|
+
],
|
|
151
|
+
"vae": [
|
|
152
|
+
"3",
|
|
153
|
+
0
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"11": {
|
|
158
|
+
"class_type": "CreateVideo",
|
|
159
|
+
"inputs": {
|
|
160
|
+
"images": [
|
|
161
|
+
"10",
|
|
162
|
+
0
|
|
163
|
+
],
|
|
164
|
+
"fps": "$fps"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"12": {
|
|
168
|
+
"class_type": "SaveVideo",
|
|
169
|
+
"inputs": {
|
|
170
|
+
"video": [
|
|
171
|
+
"11",
|
|
172
|
+
0
|
|
173
|
+
],
|
|
174
|
+
"filename_prefix": "lcode/video",
|
|
175
|
+
"format": "mp4",
|
|
176
|
+
"codec": "auto"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"13": {
|
|
180
|
+
"class_type": "CLIPVisionLoader",
|
|
181
|
+
"inputs": {
|
|
182
|
+
"clip_name": "$clip_vision"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "ComfyUI's template video_wan2_2_5B_fun_inpaint, in API format (lcode adds the start and end images)",
|
|
3
|
+
"defaults": {
|
|
4
|
+
"width": 1280,
|
|
5
|
+
"height": 704,
|
|
6
|
+
"length": 121,
|
|
7
|
+
"fps": 24,
|
|
8
|
+
"steps": 20,
|
|
9
|
+
"negative": "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
|
|
10
|
+
},
|
|
11
|
+
"labels": {
|
|
12
|
+
"1": "loading the model",
|
|
13
|
+
"2": "loading the text encoder",
|
|
14
|
+
"3": "loading the VAE",
|
|
15
|
+
"5": "reading the prompt",
|
|
16
|
+
"6": "reading the prompt",
|
|
17
|
+
"7": "reading the first and last frames",
|
|
18
|
+
"8": "drawing the frames",
|
|
19
|
+
"9": "decoding the frames",
|
|
20
|
+
"10": "making the video",
|
|
21
|
+
"11": "saving"
|
|
22
|
+
},
|
|
23
|
+
"graph": {
|
|
24
|
+
"1": {
|
|
25
|
+
"class_type": "UNETLoader",
|
|
26
|
+
"inputs": {
|
|
27
|
+
"unet_name": "$model",
|
|
28
|
+
"weight_dtype": "default"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"2": {
|
|
32
|
+
"class_type": "CLIPLoader",
|
|
33
|
+
"inputs": {
|
|
34
|
+
"clip_name": "$text_encoder",
|
|
35
|
+
"type": "wan",
|
|
36
|
+
"device": "default"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"3": {
|
|
40
|
+
"class_type": "VAELoader",
|
|
41
|
+
"inputs": {
|
|
42
|
+
"vae_name": "$vae"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"4": {
|
|
46
|
+
"class_type": "ModelSamplingSD3",
|
|
47
|
+
"inputs": {
|
|
48
|
+
"model": [
|
|
49
|
+
"1",
|
|
50
|
+
0
|
|
51
|
+
],
|
|
52
|
+
"shift": 8
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"5": {
|
|
56
|
+
"class_type": "CLIPTextEncode",
|
|
57
|
+
"inputs": {
|
|
58
|
+
"text": "$prompt",
|
|
59
|
+
"clip": [
|
|
60
|
+
"2",
|
|
61
|
+
0
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"6": {
|
|
66
|
+
"class_type": "CLIPTextEncode",
|
|
67
|
+
"inputs": {
|
|
68
|
+
"text": "$negative",
|
|
69
|
+
"clip": [
|
|
70
|
+
"2",
|
|
71
|
+
0
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"7": {
|
|
76
|
+
"class_type": "WanFunInpaintToVideo",
|
|
77
|
+
"inputs": {
|
|
78
|
+
"positive": [
|
|
79
|
+
"5",
|
|
80
|
+
0
|
|
81
|
+
],
|
|
82
|
+
"negative": [
|
|
83
|
+
"6",
|
|
84
|
+
0
|
|
85
|
+
],
|
|
86
|
+
"vae": [
|
|
87
|
+
"3",
|
|
88
|
+
0
|
|
89
|
+
],
|
|
90
|
+
"width": "$width",
|
|
91
|
+
"height": "$height",
|
|
92
|
+
"length": "$length",
|
|
93
|
+
"batch_size": 1
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"8": {
|
|
97
|
+
"class_type": "KSampler",
|
|
98
|
+
"inputs": {
|
|
99
|
+
"model": [
|
|
100
|
+
"4",
|
|
101
|
+
0
|
|
102
|
+
],
|
|
103
|
+
"seed": "$seed",
|
|
104
|
+
"steps": "$steps",
|
|
105
|
+
"cfg": 6,
|
|
106
|
+
"sampler_name": "uni_pc",
|
|
107
|
+
"scheduler": "simple",
|
|
108
|
+
"positive": [
|
|
109
|
+
"7",
|
|
110
|
+
0
|
|
111
|
+
],
|
|
112
|
+
"negative": [
|
|
113
|
+
"7",
|
|
114
|
+
1
|
|
115
|
+
],
|
|
116
|
+
"latent_image": [
|
|
117
|
+
"7",
|
|
118
|
+
2
|
|
119
|
+
],
|
|
120
|
+
"denoise": 1
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"9": {
|
|
124
|
+
"class_type": "VAEDecode",
|
|
125
|
+
"inputs": {
|
|
126
|
+
"samples": [
|
|
127
|
+
"8",
|
|
128
|
+
0
|
|
129
|
+
],
|
|
130
|
+
"vae": [
|
|
131
|
+
"3",
|
|
132
|
+
0
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"10": {
|
|
137
|
+
"class_type": "CreateVideo",
|
|
138
|
+
"inputs": {
|
|
139
|
+
"images": [
|
|
140
|
+
"9",
|
|
141
|
+
0
|
|
142
|
+
],
|
|
143
|
+
"fps": "$fps"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"11": {
|
|
147
|
+
"class_type": "SaveVideo",
|
|
148
|
+
"inputs": {
|
|
149
|
+
"video": [
|
|
150
|
+
"10",
|
|
151
|
+
0
|
|
152
|
+
],
|
|
153
|
+
"filename_prefix": "lcode/video",
|
|
154
|
+
"format": "mp4",
|
|
155
|
+
"codec": "auto"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.45.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
lcode/__init__.py,sha256=
|
|
1
|
+
lcode/__init__.py,sha256=L3biyHRfnXUkps8h8FkUi2zcpeYOxPdd5vXFMys3I_o,473
|
|
2
2
|
lcode/__main__.py,sha256=s5N5pryX2FAu93GGZ3FxUrIXHWGzxz66lKUoaV_h8Ws,35
|
|
3
3
|
lcode/acp.py,sha256=d97VV3sbqXdWKhPQQNbYRaDTm8WrUk0mGbDLiOyX7jE,29058
|
|
4
4
|
lcode/action.py,sha256=wuT5F9EJCdEligPeytzmEi8FpahWBhXer528tpuTj_k,16711
|
|
@@ -25,14 +25,14 @@ lcode/frontmatter.py,sha256=Bx0n4vLPhp7ZCWx-g7xqzqNy5VoKBxyeeikh5UFB_gA,2441
|
|
|
25
25
|
lcode/gitflow.py,sha256=6j_nnCBZXkX1qhTI7K4CyZ5D0NAZLpH96weFEo64AkQ,27913
|
|
26
26
|
lcode/hardware.py,sha256=2VbknZCxTZJwJUD8YWdQ7ryEQo_LcjtoOBOHOU9Y0fw,3085
|
|
27
27
|
lcode/hooks.py,sha256=K0itOiAuH4rxndJ9gne_uCrqby6oWAzFPRrfB3tCq-E,9324
|
|
28
|
-
lcode/imageopts.py,sha256=
|
|
28
|
+
lcode/imageopts.py,sha256=fSVWOiYVeKXfYCdfoBedT2DB_UnZnh66xzp-uFh7N1M,9587
|
|
29
29
|
lcode/jobs.py,sha256=P1P3FqI6EqL4LgdMX9YGZwCW0Bh5BRXFA_FnQxpHNjE,5618
|
|
30
30
|
lcode/limits.py,sha256=oJzVQoMyTZ0_Fkpu5t3qxrf9v7Hv9S8U8BRQR5M1aKA,1237
|
|
31
31
|
lcode/lsp.py,sha256=NtSW7xapv7fg2HAQLuYK1nwIqT7nG0LK1hiTpOmla_4,26086
|
|
32
32
|
lcode/matching.py,sha256=2N3_qwKJ5gd8el2m_y_egKkFM9rsF1eUhYJcRCVIp98,14015
|
|
33
|
-
lcode/media.py,sha256=
|
|
34
|
-
lcode/media.toml,sha256=
|
|
35
|
-
lcode/media_catalog.py,sha256=
|
|
33
|
+
lcode/media.py,sha256=7Sq4fKRLNGMJekZp9mrRnyMJHfTjBE2Z13DaTM0v5aA,36618
|
|
34
|
+
lcode/media.toml,sha256=Tbw9EsKk0vepKsVBS5OorzPHYEsib--UjRk7aRbh00o,8027
|
|
35
|
+
lcode/media_catalog.py,sha256=kvEm3x48GdP2jH1XciZlWyXxxsAGjT6jt6heDcgYUtY,14614
|
|
36
36
|
lcode/memory.py,sha256=WZEHTp-lVxAwS-yyLQR2xB3TMZ9sejmX2Ivg7T-tl3w,21949
|
|
37
37
|
lcode/models.toml,sha256=lt2G64zzo_Ryxv2ISRSNZoVD_JqCqYqsM01n8WoXGs8,4098
|
|
38
38
|
lcode/modes.py,sha256=MG2OuwGxzbPxzP5ax_pdPPxMsjEd5Lv4kCQbCoFKaNo,5095
|
|
@@ -43,7 +43,7 @@ lcode/planning.py,sha256=Q2upaoZPnXDMQT2F-ETxX1lk_wDntTzhSKNVYIQSoQA,6540
|
|
|
43
43
|
lcode/preview.py,sha256=ATzEs6yaI1__bfo2_juWznrTDXUD4QTc_aQMPRCytgA,6253
|
|
44
44
|
lcode/render.py,sha256=gMvf4W3qkLUJ4Vot3az_hP_zRDAItJC9pP-68dHFkfA,1375
|
|
45
45
|
lcode/repeats.py,sha256=OTrPdb6BKArDLQEgHaXmJZ-tIFsY_1bk4P0wzzANKLY,5964
|
|
46
|
-
lcode/repl.py,sha256=
|
|
46
|
+
lcode/repl.py,sha256=qOKk1zMCmwmOPayhbIcnXZPiBMZJzJI-c4NeR1bFwPY,73952
|
|
47
47
|
lcode/repomap.py,sha256=1GRJgEUjBl9XHSXsF7tSGXBCEgsY-3CzwMohaTudZ_M,13969
|
|
48
48
|
lcode/residency.py,sha256=B9pwqG6yx5aQD2M5xyQ-bMnYmhD7fv3wCVw_X6VIeAo,7012
|
|
49
49
|
lcode/review.py,sha256=0QBIS-R1B2b3zqx5TcC1qZptpKBbrDhYvMKSNCaDAUo,4686
|
|
@@ -78,10 +78,12 @@ lcode/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
78
78
|
lcode/workflows/flux2-klein-4b-t2i.json,sha256=yGrYDT8pG2bns9DS1JavhlX5fwhDgIKfAwrHrRCyE8k,1788
|
|
79
79
|
lcode/workflows/remove-background.json,sha256=iMNaXqlkcWN-mCQWezD3NUbW94cHN3JcPYsSUsfOl64,794
|
|
80
80
|
lcode/workflows/upscale-esrgan.json,sha256=quDmTnncvycggskgvw6zvfNsub7mS0WY3a4J99fWeXc,745
|
|
81
|
+
lcode/workflows/wan21-fun-camera.json,sha256=raXnj6-Y7gyJlOcxFRVSbMPihQA096nHkmyCq5xcASg,3898
|
|
82
|
+
lcode/workflows/wan22-fun-inp-5b.json,sha256=4HbKEezik8MHvxTMP6PjQUPQPfCeSu10U9FrC-DHKYw,3327
|
|
81
83
|
lcode/workflows/wan22-ti2v-5b.json,sha256=RcTehoC2pNizzlb7Ed3keJ56DiE7V5lhXmsvR2Y0smQ,3371
|
|
82
84
|
lcode/workflows/z-image-turbo-t2i.json,sha256=sHsL7xnnuULOuWGYlQrDl4jOTySdAmb2T82c_cChv0s,1585
|
|
83
|
-
lcode_cli-0.
|
|
84
|
-
lcode_cli-0.
|
|
85
|
-
lcode_cli-0.
|
|
86
|
-
lcode_cli-0.
|
|
87
|
-
lcode_cli-0.
|
|
85
|
+
lcode_cli-0.45.0.dist-info/METADATA,sha256=9tGfBLjjwTn6hcRdnFeCmqQRdwutAUzc5uKz8cuo95I,13469
|
|
86
|
+
lcode_cli-0.45.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
87
|
+
lcode_cli-0.45.0.dist-info/entry_points.txt,sha256=Hxsn_itbnbgft3U0FwxWdmEt0yoAV2LB-nJDnL9WT20,172
|
|
88
|
+
lcode_cli-0.45.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
|
|
89
|
+
lcode_cli-0.45.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|