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 CHANGED
@@ -3,7 +3,7 @@
3
3
  From a program: `from lcode import Session` (see lcode.api).
4
4
  """
5
5
 
6
- __version__ = "0.44.0"
6
+ __version__ = "0.45.0"
7
7
 
8
8
  API = ("Session", "Result", "Options", "SetupError")
9
9
 
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(f"unknown option {raw_key!r}: use n, ar (or size), seed, quality, style, no or seconds")
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[:1]],
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
- model = current(agent, "video")
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 start is not None:
517
- try:
518
- name = server.upload(start)
519
- except (comfy.ComfyError, OSError) as e:
520
- raise MediaError(f"Couldn't hand the image to ComfyUI: {e}") from e
521
- latent = edits.node_of(graph, "Wan22ImageToVideoLatent")
522
- if latent is None:
523
- raise MediaError(f"{model.name} can't start from an image.")
524
- graph["8100"] = {"class_type": "LoadImage", "inputs": {"image": name}}
525
- graph[latent]["inputs"]["start_image"] = ["8100", 0]
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 ""}) # fmt: skip
530
- remembered = {k: job.get(k) for k in ("kind", "prompt", "sources", "mask", "pads", "keep_shape")}
531
- plan = Plan(model, workflow, graph, meta, prompt, seed, options, "video", job.get("sources") or [], remembered)
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": "Image mode: outpaint an image to a shape or by sides, e.g. /extend 3 --ar 16:9 (or --left 256)",
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") # what video mode shares
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.44.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=RsG17qPpn7di8jUUOV-g7wyfYOlPM9AIXTiqeUvvG8Y,473
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=msg8Y3OoP6sg1j3L_0K_WhtwdkV2fQl4_IUMq0QsoCE,8610
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=MZ4ZSuehInueDRvyYCm1BXRePM5IWk5NE23VGw37C8c,33647
34
- lcode/media.toml,sha256=gEVwlSLYWOFsYaLj_3J6crY5J6weHYKaqgITtHXs2q4,6010
35
- lcode/media_catalog.py,sha256=ABHlhvZEAZmei1YzNs70_1kyaS0h0TEzbLZWLISNCkk,14363
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=VLbVvjGtukGUXOP-Wuj29KbGcLiDnD57CoDexW3dUFU,73153
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.44.0.dist-info/METADATA,sha256=bRKpVWXONhz8JdV7UgzC7HdXXCvNHiKaet245hR8-4Q,13469
84
- lcode_cli-0.44.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
85
- lcode_cli-0.44.0.dist-info/entry_points.txt,sha256=Hxsn_itbnbgft3U0FwxWdmEt0yoAV2LB-nJDnL9WT20,172
86
- lcode_cli-0.44.0.dist-info/licenses/LICENSE,sha256=JWjmoL7nNIC1XUa-dZm2hwLUgjUQMFo6k8APb0idNzs,1074
87
- lcode_cli-0.44.0.dist-info/RECORD,,
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,,