cinderholm commited on
Commit
1b93f28
·
verified ·
1 Parent(s): 1e46db0

copy of thornmaze/wan2-2-i2v-v3 (byte-1:1)

Browse files
Files changed (9) hide show
  1. README.md +5 -6
  2. aoti.py +35 -0
  3. app.py +725 -0
  4. lora_loader.py +201 -0
  5. model/loss.py +128 -0
  6. model/pytorch_msssim/__init__.py +198 -0
  7. model/warplayer.py +24 -0
  8. packages.txt +1 -0
  9. requirements.txt +19 -0
README.md CHANGED
@@ -1,11 +1,10 @@
1
  ---
2
- title: Wan2 2 I2v V3
3
- emoji: 🐢
4
- colorFrom: blue
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  ---
 
1
  ---
2
+ title: Wan2.2 14B Fast Preview [NEW]
3
+ emoji: 🏆
4
+ colorFrom: indigo
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 6.0.1
 
8
  app_file: app.py
9
  pinned: false
10
  ---
aoti.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ """
3
+
4
+ from typing import cast
5
+
6
+ import torch
7
+ from huggingface_hub import hf_hub_download
8
+ from spaces.zero.torch.aoti import ZeroGPUCompiledModel
9
+ from spaces.zero.torch.aoti import ZeroGPUWeights
10
+ from torch._functorch._aot_autograd.subclass_parametrization import unwrap_tensor_subclass_parameters
11
+
12
+
13
+ def _shallow_clone_module(module: torch.nn.Module) -> torch.nn.Module:
14
+ clone = object.__new__(module.__class__)
15
+ clone.__dict__ = module.__dict__.copy()
16
+ clone._parameters = module._parameters.copy()
17
+ clone._buffers = module._buffers.copy()
18
+ clone._modules = {k: _shallow_clone_module(v) for k, v in module._modules.items() if v is not None}
19
+ return clone
20
+
21
+
22
+ def aoti_blocks_load(module: torch.nn.Module, repo_id: str, variant: str | None = None):
23
+ repeated_blocks = cast(list[str], module._repeated_blocks)
24
+ aoti_files = {name: hf_hub_download(
25
+ repo_id=repo_id,
26
+ filename='package.pt2',
27
+ subfolder=name if variant is None else f'{name}.{variant}',
28
+ ) for name in repeated_blocks}
29
+ for block_name, aoti_file in aoti_files.items():
30
+ for block in module.modules():
31
+ if block.__class__.__name__ == block_name:
32
+ block_ = _shallow_clone_module(block)
33
+ unwrap_tensor_subclass_parameters(block_)
34
+ weights = ZeroGPUWeights(block_.state_dict())
35
+ block.forward = ZeroGPUCompiledModel(aoti_file, weights)
app.py ADDED
@@ -0,0 +1,725 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os; os.system('pip install --upgrade --no-deps spaces')
2
+ import spaces
3
+ import shutil
4
+ import subprocess
5
+ import sys
6
+ import copy
7
+ import random
8
+ import tempfile
9
+ import warnings
10
+ import time
11
+ import gc
12
+ import uuid
13
+ from tqdm import tqdm
14
+ import cv2
15
+ import numpy as np
16
+ import torch
17
+ import torch._dynamo
18
+ from torch.nn import functional as F
19
+ from PIL import Image
20
+
21
+ import gradio as gr
22
+ from diffusers import (
23
+ FlowMatchEulerDiscreteScheduler,
24
+ SASolverScheduler,
25
+ DEISMultistepScheduler,
26
+ DPMSolverMultistepInverseScheduler,
27
+ UniPCMultistepScheduler,
28
+ DPMSolverMultistepScheduler,
29
+ DPMSolverSinglestepScheduler,
30
+ )
31
+ from diffusers.pipelines.wan.pipeline_wan_i2v import WanImageToVideoPipeline
32
+ from diffusers.utils.export_utils import export_to_video
33
+
34
+ from torchao.quantization import quantize_, Float8DynamicActivationFloat8WeightConfig, Int8WeightOnlyConfig
35
+ import aoti
36
+ import lora_loader
37
+
38
+ os.environ["TOKENIZERS_PARALLELISM"] = "true"
39
+ warnings.filterwarnings("ignore")
40
+ IS_ZERO_GPU = bool(os.getenv("SPACES_ZERO_GPU"))
41
+
42
+ # if IS_ZERO_GPU:
43
+ # print("Loading...")
44
+ # subprocess.run("rm -rf /data-nvme/zerogpu-offload/*", env={}, shell=True)
45
+
46
+ # --- FRAME EXTRACTION JS & LOGIC ---
47
+
48
+ # JS to grab timestamp from the output video
49
+ get_timestamp_js = """
50
+ function() {
51
+ // Select the video element specifically inside the component with id 'generated-video'
52
+ const video = document.querySelector('#generated-video video');
53
+
54
+ if (video) {
55
+ console.log("Video found! Time: " + video.currentTime);
56
+ return video.currentTime;
57
+ } else {
58
+ console.log("No video element found.");
59
+ return 0;
60
+ }
61
+ }
62
+ """
63
+
64
+
65
+ def extract_frame(video_path, timestamp):
66
+ # Safety check: if no video is present
67
+ if not video_path:
68
+ return None
69
+
70
+ print(f"Extracting frame at timestamp: {timestamp}")
71
+
72
+ cap = cv2.VideoCapture(video_path)
73
+
74
+ if not cap.isOpened():
75
+ return None
76
+
77
+ # Calculate frame number
78
+ fps = cap.get(cv2.CAP_PROP_FPS)
79
+ target_frame_num = int(float(timestamp) * fps)
80
+
81
+ # Cap total frames to prevent errors at the very end of video
82
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
83
+ if target_frame_num >= total_frames:
84
+ target_frame_num = total_frames - 1
85
+
86
+ # Set position
87
+ cap.set(cv2.CAP_PROP_POS_FRAMES, target_frame_num)
88
+ ret, frame = cap.read()
89
+ cap.release()
90
+
91
+ if ret:
92
+ # Convert from BGR (OpenCV) to RGB (Gradio)
93
+ # Gradio Image component handles Numpy array -> PIL conversion automatically
94
+ return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
95
+
96
+ return None
97
+
98
+ # --- END FRAME EXTRACTION LOGIC ---
99
+
100
+
101
+ def clear_vram():
102
+ gc.collect()
103
+ torch.cuda.empty_cache()
104
+
105
+
106
+ # RIFE
107
+ if not os.path.exists("RIFEv4.26_0921.zip"):
108
+ print("Downloading RIFE Model...")
109
+ subprocess.run([
110
+ "wget", "-q",
111
+ "https://huggingface.co/thornmaze/RIFE/resolve/main/RIFEv4.26_0921.zip",
112
+ "-O", "RIFEv4.26_0921.zip"
113
+ ], check=True)
114
+ subprocess.run(["unzip", "-o", "RIFEv4.26_0921.zip"], check=True)
115
+
116
+ # sys.path.append(os.getcwd())
117
+
118
+ from train_log.RIFE_HDv3 import Model
119
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
120
+ rife_model = Model()
121
+ rife_model.load_model("train_log", -1)
122
+ rife_model.eval()
123
+
124
+
125
+ @torch.no_grad()
126
+ def interpolate_bits(frames_np, multiplier=2, scale=1.0):
127
+ """
128
+ Interpolation maintaining Numpy Float 0-1 format.
129
+ Args:
130
+ frames_np: Numpy Array (Time, Height, Width, Channels) - Float32 [0.0, 1.0]
131
+ multiplier: int (2, 4, 8)
132
+ Returns:
133
+ List of Numpy Arrays (Height, Width, Channels) - Float32 [0.0, 1.0]
134
+ """
135
+
136
+ # Handle input shape
137
+ if isinstance(frames_np, list):
138
+ # Convert list of arrays to one big array for easier shape handling if needed,
139
+ # but here we just grab dims from first frame
140
+ T = len(frames_np)
141
+ H, W, C = frames_np[0].shape
142
+ else:
143
+ T, H, W, C = frames_np.shape
144
+
145
+ # 1. No Interpolation Case
146
+ if multiplier < 2:
147
+ # Just convert 4D array to list of 3D arrays
148
+ if isinstance(frames_np, np.ndarray):
149
+ return list(frames_np)
150
+ return frames_np
151
+
152
+ n_interp = multiplier - 1
153
+
154
+ # Pre-calc padding for RIFE (requires dimensions divisible by 32/scale)
155
+ tmp = max(128, int(128 / scale))
156
+ ph = ((H - 1) // tmp + 1) * tmp
157
+ pw = ((W - 1) // tmp + 1) * tmp
158
+ padding = (0, pw - W, 0, ph - H)
159
+
160
+ # Helper: Numpy (H, W, C) Float -> Tensor (1, C, H, W) Half
161
+ def to_tensor(frame_np):
162
+ # frame_np is float32 0-1
163
+ t = torch.from_numpy(frame_np).to(device)
164
+ # HWC -> CHW
165
+ t = t.permute(2, 0, 1).unsqueeze(0)
166
+ return F.pad(t, padding).half()
167
+
168
+ # Helper: Tensor (1, C, H, W) Half -> Numpy (H, W, C) Float
169
+ def from_tensor(tensor):
170
+ # Crop padding
171
+ t = tensor[0, :, :H, :W]
172
+ # CHW -> HWC
173
+ t = t.permute(1, 2, 0)
174
+ # Keep as float32, range 0-1
175
+ return t.float().cpu().numpy()
176
+
177
+ def make_inference(I0, I1, n):
178
+ if rife_model.version >= 3.9:
179
+ res = []
180
+ for i in range(n):
181
+ res.append(rife_model.inference(I0, I1, (i+1) * 1. / (n+1), scale))
182
+ return res
183
+ else:
184
+ middle = rife_model.inference(I0, I1, scale)
185
+ if n == 1:
186
+ return [middle]
187
+ first_half = make_inference(I0, middle, n=n//2)
188
+ second_half = make_inference(middle, I1, n=n//2)
189
+ if n % 2:
190
+ return [*first_half, middle, *second_half]
191
+ else:
192
+ return [*first_half, *second_half]
193
+
194
+ output_frames = []
195
+
196
+ # Process Frames
197
+ # Load first frame into GPU
198
+ I1 = to_tensor(frames_np[0])
199
+
200
+ total_steps = T - 1
201
+
202
+ with tqdm(total=total_steps, desc="Interpolating", unit="frame") as pbar:
203
+
204
+ for i in range(total_steps):
205
+ I0 = I1
206
+ # Add original frame to output
207
+ output_frames.append(from_tensor(I0))
208
+
209
+ # Load next frame
210
+ I1 = to_tensor(frames_np[i+1])
211
+
212
+ # Generate intermediate frames
213
+ mid_tensors = make_inference(I0, I1, n_interp)
214
+
215
+ # Append intermediate frames
216
+ for mid in mid_tensors:
217
+ output_frames.append(from_tensor(mid))
218
+
219
+ if (i + 1) % 50 == 0:
220
+ pbar.update(50)
221
+ pbar.update(total_steps % 50)
222
+
223
+ # Add the very last frame
224
+ output_frames.append(from_tensor(I1))
225
+
226
+ # Cleanup
227
+ del I0, I1, mid_tensors
228
+ torch.cuda.empty_cache()
229
+
230
+ return output_frames
231
+
232
+
233
+ # WAN
234
+
235
+ MODEL_ID = "thornmaze/WAMU_v3_WAN2.2_I2V_LIGHTNING"
236
+
237
+ LORA_MODELS = []
238
+
239
+ MAX_DIM = 832
240
+ MIN_DIM = 480
241
+ SQUARE_DIM = 640
242
+ MULTIPLE_OF = 16
243
+ MAX_SEED = np.iinfo(np.int32).max
244
+
245
+ FIXED_FPS = 16
246
+ MIN_FRAMES_MODEL = 8
247
+ MAX_FRAMES_MODEL = 321
248
+
249
+ MIN_DURATION = round(MIN_FRAMES_MODEL / FIXED_FPS, 1)
250
+ MAX_DURATION = round(MAX_FRAMES_MODEL / FIXED_FPS, 1)
251
+
252
+ SCHEDULER_MAP = {
253
+ "FlowMatchEulerDiscrete": FlowMatchEulerDiscreteScheduler,
254
+ "SASolver": SASolverScheduler,
255
+ "DEISMultistep": DEISMultistepScheduler,
256
+ "DPMSolverMultistepInverse": DPMSolverMultistepInverseScheduler,
257
+ "UniPCMultistep": UniPCMultistepScheduler,
258
+ "DPMSolverMultistep": DPMSolverMultistepScheduler,
259
+ "DPMSolverSinglestep": DPMSolverSinglestepScheduler,
260
+ }
261
+
262
+ pipe = WanImageToVideoPipeline.from_pretrained(
263
+ MODEL_ID,
264
+ torch_dtype=torch.bfloat16,
265
+ ).to('cuda')
266
+ original_scheduler = copy.deepcopy(pipe.scheduler)
267
+
268
+ for i, lora in enumerate(LORA_MODELS):
269
+ name_high_tr = lora["high_tr"].split(".")[0].split("/")[-1] + "Hh"
270
+ name_low_tr = lora["low_tr"].split(".")[0].split("/")[-1] + "Ll"
271
+
272
+ try:
273
+ pipe.load_lora_weights(
274
+ lora["repo_id"],
275
+ weight_name=lora["high_tr"],
276
+ adapter_name=name_high_tr
277
+ )
278
+
279
+ kwargs_lora = {"load_into_transformer_2": True}
280
+ pipe.load_lora_weights(
281
+ lora["repo_id"],
282
+ weight_name=lora["low_tr"],
283
+ adapter_name=name_low_tr,
284
+ **kwargs_lora
285
+ )
286
+
287
+ pipe.set_adapters([name_high_tr, name_low_tr], adapter_weights=[1.0, 1.0])
288
+
289
+ pipe.fuse_lora(adapter_names=[name_high_tr], lora_scale=lora["high_scale"], components=["transformer"])
290
+ pipe.fuse_lora(adapter_names=[name_low_tr], lora_scale=lora["low_scale"], components=["transformer_2"])
291
+
292
+ pipe.unload_lora_weights()
293
+
294
+ print(f"Applied: {lora['high_tr']}, hs={lora['high_scale']}/ls={lora['low_scale']}, {i+1}/{len(LORA_MODELS)}")
295
+ except Exception as e:
296
+ print("Error:", str(e))
297
+ print("Failed LoRA:", name_high_tr)
298
+ pipe.unload_lora_weights()
299
+
300
+ # if os.path.exists(CACHE_DIR):
301
+ # shutil.rmtree(CACHE_DIR)
302
+ # print("Deleted Hugging Face cache.")
303
+ # else:
304
+ # print("No hub cache found.")
305
+
306
+ quantize_(pipe.text_encoder, Int8WeightOnlyConfig())
307
+ torch._dynamo.reset()
308
+ quantize_(pipe.transformer, Float8DynamicActivationFloat8WeightConfig())
309
+ torch._dynamo.reset()
310
+ quantize_(pipe.transformer_2, Float8DynamicActivationFloat8WeightConfig())
311
+ torch._dynamo.reset()
312
+
313
+ spaces.aoti_load(
314
+ module=pipe.transformer,
315
+ repo_id='thornmaze/WanTransformer3DModel-sm120-cu130-raa',
316
+ )
317
+ spaces.aoti_load(
318
+ module=pipe.transformer_2,
319
+ repo_id='thornmaze/WanTransformer3DModel-sm120-cu130-raa',
320
+ )
321
+
322
+ # pipe.vae.enable_slicing()
323
+ # pipe.vae.enable_tiling()
324
+
325
+ default_prompt_i2v = "make this image come alive, cinematic motion, smooth animation"
326
+ default_negative_prompt = "色调艳丽, 过曝, 静态, 细节模糊不清, 字幕, 风格, 作品, 画作, 画面, 静止, 整体发灰, 最差质量, 低质量, JPEG压缩残留, 丑陋的, 残缺的, 多余的手指, 画得不好的手部, 画得不好的脸部, 畸形的, 毁容的, 形态畸形的肢体, 手指融合, 静止不动的画面, 杂乱的背景, 三条腿, 背景人很多, 倒着走"
327
+
328
+
329
+ def model_title():
330
+ return "## Wan 2.2 I2V 14B Lightning — NSFW"
331
+
332
+
333
+ def resize_image(image: Image.Image) -> Image.Image:
334
+ width, height = image.size
335
+ if width == height:
336
+ return image.resize((SQUARE_DIM, SQUARE_DIM), Image.LANCZOS)
337
+
338
+ aspect_ratio = width / height
339
+ MAX_ASPECT_RATIO = MAX_DIM / MIN_DIM
340
+ MIN_ASPECT_RATIO = MIN_DIM / MAX_DIM
341
+
342
+ image_to_resize = image
343
+ if aspect_ratio > MAX_ASPECT_RATIO:
344
+ target_w, target_h = MAX_DIM, MIN_DIM
345
+ crop_width = int(round(height * MAX_ASPECT_RATIO))
346
+ left = (width - crop_width) // 2
347
+ image_to_resize = image.crop((left, 0, left + crop_width, height))
348
+ elif aspect_ratio < MIN_ASPECT_RATIO:
349
+ target_w, target_h = MIN_DIM, MAX_DIM
350
+ crop_height = int(round(width / MIN_ASPECT_RATIO))
351
+ top = (height - crop_height) // 2
352
+ image_to_resize = image.crop((0, top, width, top + crop_height))
353
+ else:
354
+ if width > height:
355
+ target_w = MAX_DIM
356
+ target_h = int(round(target_w / aspect_ratio))
357
+ else:
358
+ target_h = MAX_DIM
359
+ target_w = int(round(target_h * aspect_ratio))
360
+
361
+ final_w = round(target_w / MULTIPLE_OF) * MULTIPLE_OF
362
+ final_h = round(target_h / MULTIPLE_OF) * MULTIPLE_OF
363
+ final_w = max(MIN_DIM, min(MAX_DIM, final_w))
364
+ final_h = max(MIN_DIM, min(MAX_DIM, final_h))
365
+ return image_to_resize.resize((final_w, final_h), Image.LANCZOS)
366
+
367
+
368
+ def resize_and_crop_to_match(target_image, reference_image):
369
+ ref_width, ref_height = reference_image.size
370
+ target_width, target_height = target_image.size
371
+ scale = max(ref_width / target_width, ref_height / target_height)
372
+ new_width, new_height = int(target_width * scale), int(target_height * scale)
373
+ resized = target_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
374
+ left, top = (new_width - ref_width) // 2, (new_height - ref_height) // 2
375
+ return resized.crop((left, top, left + ref_width, top + ref_height))
376
+
377
+
378
+ def get_num_frames(duration_seconds: float):
379
+ raw = int(round(duration_seconds * FIXED_FPS))
380
+ raw = max(MIN_FRAMES_MODEL, min(MAX_FRAMES_MODEL, raw))
381
+ return ((raw - 1) // 4) * 4 + 1
382
+
383
+
384
+ def get_inference_duration(
385
+ resized_image,
386
+ processed_last_image,
387
+ prompt,
388
+ steps,
389
+ negative_prompt,
390
+ num_frames,
391
+ guidance_scale,
392
+ guidance_scale_2,
393
+ current_seed,
394
+ scheduler_name,
395
+ flow_shift,
396
+ frame_multiplier,
397
+ quality,
398
+ duration_seconds,
399
+ safe_mode,
400
+ lora_groups,
401
+ progress
402
+ ):
403
+ BASE_FRAMES_HEIGHT_WIDTH = 81 * 832 * 624
404
+ BASE_STEP_DURATION = 5.
405
+ width, height = resized_image.size
406
+ factor = num_frames * width * height / BASE_FRAMES_HEIGHT_WIDTH
407
+ step_duration = BASE_STEP_DURATION * factor ** 1.5
408
+ gen_time = int(steps) * step_duration
409
+
410
+ if guidance_scale > 1:
411
+ gen_time = gen_time * 2.4
412
+
413
+ frame_factor = frame_multiplier // FIXED_FPS
414
+ if frame_factor > 1:
415
+ total_out_frames = (num_frames * frame_factor) - num_frames
416
+ inter_time = (total_out_frames * 0.02)
417
+ gen_time += inter_time
418
+
419
+ total_time = 15 + gen_time
420
+ if safe_mode:
421
+ total_time = total_time * 1.30
422
+
423
+ return total_time
424
+
425
+
426
+ @spaces.GPU(duration=get_inference_duration, size='xlarge')
427
+ def run_inference(
428
+ resized_image,
429
+ processed_last_image,
430
+ prompt,
431
+ steps,
432
+ negative_prompt,
433
+ num_frames,
434
+ guidance_scale,
435
+ guidance_scale_2,
436
+ current_seed,
437
+ scheduler_name,
438
+ flow_shift,
439
+ frame_multiplier,
440
+ quality,
441
+ duration_seconds,
442
+ safe_mode=False,
443
+ lora_groups=None,
444
+ progress=gr.Progress(track_tqdm=True),
445
+ ):
446
+ scheduler_class = SCHEDULER_MAP.get(scheduler_name)
447
+ if scheduler_class.__name__ != pipe.scheduler.config._class_name or flow_shift != pipe.scheduler.config.get("flow_shift", "shift"):
448
+ config = copy.deepcopy(original_scheduler.config)
449
+ if scheduler_class == FlowMatchEulerDiscreteScheduler:
450
+ config['shift'] = flow_shift
451
+ else:
452
+ config['flow_shift'] = flow_shift
453
+ pipe.scheduler = scheduler_class.from_config(config)
454
+
455
+ clear_vram()
456
+
457
+ task_name = str(uuid.uuid4())[:8]
458
+ print(f"Generating {num_frames} frames, task: {task_name}, {duration_seconds}, {resized_image.size}, lora={lora_groups}")
459
+ start = time.time()
460
+
461
+ lora_loaded = False
462
+ if lora_groups:
463
+ try:
464
+ for idx, name in enumerate(lora_groups):
465
+ if name and name != "(None)":
466
+ lora_loader.load_lora_to_pipe(pipe, name, adapter_name=f"lora_{idx}")
467
+ lora_loaded = True
468
+ print(f"LoRA loaded: {lora_groups}")
469
+ except Exception as e:
470
+ print(f"LoRA warning: {e}")
471
+
472
+ result = pipe(
473
+ image=resized_image,
474
+ last_image=processed_last_image,
475
+ prompt=prompt,
476
+ negative_prompt=negative_prompt,
477
+ height=resized_image.height,
478
+ width=resized_image.width,
479
+ num_frames=num_frames,
480
+ guidance_scale=float(guidance_scale),
481
+ guidance_scale_2=float(guidance_scale_2),
482
+ num_inference_steps=int(steps),
483
+ generator=torch.Generator(device="cuda").manual_seed(current_seed),
484
+ output_type="np"
485
+ )
486
+
487
+ if lora_loaded:
488
+ lora_loader.unload_lora(pipe)
489
+
490
+ print("gen time passed:", time.time() - start)
491
+
492
+ raw_frames_np = result.frames[0] # Returns (T, H, W, C) float32
493
+ pipe.scheduler = original_scheduler
494
+
495
+ frame_factor = frame_multiplier // FIXED_FPS
496
+ if frame_factor > 1:
497
+ start = time.time()
498
+ print(f"Processing frames (RIFE Multiplier: {frame_factor}x)...")
499
+ rife_model.device()
500
+ rife_model.flownet = rife_model.flownet.half()
501
+ final_frames = interpolate_bits(raw_frames_np, multiplier=int(frame_factor))
502
+ print("Interpolation time passed:", time.time() - start)
503
+ else:
504
+ final_frames = list(raw_frames_np)
505
+
506
+ final_fps = FIXED_FPS * int(frame_factor)
507
+
508
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as tmpfile:
509
+ video_path = tmpfile.name
510
+
511
+ start = time.time()
512
+ with tqdm(total=3, desc="Rendering Media", unit="clip") as pbar:
513
+ pbar.update(2)
514
+ export_to_video(final_frames, video_path, fps=final_fps, quality=quality)
515
+ pbar.update(1)
516
+ print(f"Export time passed, {final_fps} FPS:", time.time() - start)
517
+
518
+ return video_path, task_name
519
+
520
+
521
+ def generate_video(
522
+ input_image,
523
+ last_image,
524
+ prompt,
525
+ steps=4,
526
+ negative_prompt=default_negative_prompt,
527
+ duration_seconds=MAX_DURATION,
528
+ guidance_scale=1,
529
+ guidance_scale_2=1,
530
+ seed=42,
531
+ randomize_seed=False,
532
+ quality=5,
533
+ scheduler="UniPCMultistep",
534
+ flow_shift=6.0,
535
+ frame_multiplier=16,
536
+ safe_mode=False,
537
+ lora_groups=None,
538
+ video_component=True,
539
+ progress=gr.Progress(track_tqdm=True),
540
+ ):
541
+ """
542
+ Generate a video from an input image using the Wan 2.2 14B I2V model with Lightning LoRA.
543
+ This function takes an input image and generates a video animation based on the provided
544
+ prompt and parameters. It uses an FP8 qunatized Wan 2.2 14B Image-to-Video model in with Lightning LoRA
545
+ for fast generation in 4-8 steps.
546
+ Args:
547
+ input_image (PIL.Image): The input image to animate. Will be resized to target dimensions.
548
+ last_image (PIL.Image, optional): The optional last image for the video.
549
+ prompt (str): Text prompt describing the desired animation or motion.
550
+ steps (int, optional): Number of inference steps. More steps = higher quality but slower.
551
+ Defaults to 4. Range: 1-30.
552
+ negative_prompt (str, optional): Negative prompt to avoid unwanted elements.
553
+ Defaults to default_negative_prompt (contains unwanted visual artifacts).
554
+ duration_seconds (float, optional): Duration of the generated video in seconds.
555
+ Defaults to 2. Clamped between MIN_FRAMES_MODEL/FIXED_FPS and MAX_FRAMES_MODEL/FIXED_FPS.
556
+ guidance_scale (float, optional): Controls adherence to the prompt. Higher values = more adherence.
557
+ Defaults to 1.0. Range: 0.0-20.0.
558
+ guidance_scale_2 (float, optional): Controls adherence to the prompt. Higher values = more adherence.
559
+ Defaults to 1.0. Range: 0.0-20.0.
560
+ seed (int, optional): Random seed for reproducible results. Defaults to 42.
561
+ Range: 0 to MAX_SEED (2147483647).
562
+ randomize_seed (bool, optional): Whether to use a random seed instead of the provided seed.
563
+ Defaults to False.
564
+ quality (float, optional): Video output quality. Default is 5. Uses variable bit rate.
565
+ Highest quality is 10, lowest is 1.
566
+ scheduler (str, optional): The name of the scheduler to use for inference. Defaults to "UniPCMultistep".
567
+ flow_shift (float, optional): The flow shift value for compatible schedulers. Defaults to 6.0.
568
+ frame_multiplier (int, optional): The int value for fps enhancer
569
+ video_component(bool, optional): Show video player in output.
570
+ Defaults to True.
571
+ progress (gr.Progress, optional): Gradio progress tracker. Defaults to gr.Progress(track_tqdm=True).
572
+ Returns:
573
+ tuple: A tuple containing:
574
+ - video_path (str): Path for the video component.
575
+ - video_path (str): Path for the file download component. Attempt to avoid reconversion in video component.
576
+ - current_seed (int): The seed used for generation.
577
+ Raises:
578
+ gr.Error: If input_image is None (no image uploaded).
579
+ Note:
580
+ - Frame count is calculated as duration_seconds * FIXED_FPS (24)
581
+ - Output dimensions are adjusted to be multiples of MOD_VALUE (32)
582
+ - The function uses GPU acceleration via the @spaces.GPU decorator
583
+ - Generation time varies based on steps and duration (see get_duration function)
584
+ """
585
+
586
+ if input_image is None:
587
+ raise gr.Error("Please upload an input image.")
588
+
589
+ num_frames = get_num_frames(duration_seconds)
590
+ current_seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
591
+ resized_image = resize_image(input_image)
592
+
593
+ processed_last_image = None
594
+ if last_image:
595
+ processed_last_image = resize_and_crop_to_match(last_image, resized_image)
596
+
597
+ video_path, task_n = run_inference(
598
+ resized_image,
599
+ processed_last_image,
600
+ prompt,
601
+ steps,
602
+ negative_prompt,
603
+ num_frames,
604
+ guidance_scale,
605
+ guidance_scale_2,
606
+ current_seed,
607
+ scheduler,
608
+ flow_shift,
609
+ frame_multiplier,
610
+ quality,
611
+ duration_seconds,
612
+ safe_mode,
613
+ lora_groups,
614
+ progress,
615
+ )
616
+ print(f"GPU complete: {task_n}")
617
+
618
+ return (video_path if video_component else None), video_path, current_seed
619
+
620
+
621
+ CSS = """
622
+ #hidden-timestamp {
623
+ opacity: 0;
624
+ height: 0px;
625
+ width: 0px;
626
+ margin: 0px;
627
+ padding: 0px;
628
+ overflow: hidden;
629
+ position: absolute;
630
+ pointer-events: none;
631
+ }
632
+ """
633
+
634
+
635
+ with gr.Blocks(delete_cache=(3600, 10800)) as demo:
636
+ gr.Markdown(model_title())
637
+ gr.Markdown("Run Wan 2.2 in just 4-8 steps, fp8 quantization & AoT compilation - compatible with 🧨 diffusers and ZeroGPU")
638
+
639
+ with gr.Row():
640
+ with gr.Column():
641
+ input_image_component = gr.Image(type="pil", label="Input Image", sources=["upload", "clipboard"])
642
+ prompt_input = gr.Textbox(label="Prompt", value=default_prompt_i2v)
643
+ duration_seconds_input = gr.Slider(minimum=MIN_DURATION, maximum=MAX_DURATION, step=0.1, value=3.5, label="Duration (seconds)", info=f"Clamped to model's {MIN_FRAMES_MODEL}-{MAX_FRAMES_MODEL} frames at {FIXED_FPS}fps.")
644
+ frame_multi = gr.Dropdown(
645
+ choices=[FIXED_FPS, FIXED_FPS*2, FIXED_FPS*4, FIXED_FPS*8],
646
+ value=FIXED_FPS,
647
+ label="Video Fluidity (Frames per Second)",
648
+ info="Extra frames will be generated using flow estimation, which estimates motion between frames to make the video smoother."
649
+ )
650
+ safe_mode_checkbox = gr.Checkbox(
651
+ label="🛠️ Safe Mode",
652
+ value=True,
653
+ info="Requests 30% extra processing time to try to prevent unfinished tasks when the server is busy."
654
+ )
655
+ with gr.Accordion("Advanced Settings", open=False):
656
+ last_image_component = gr.Image(type="pil", label="Last Image (Optional)", sources=["upload", "clipboard"])
657
+ negative_prompt_input = gr.Textbox(label="Negative Prompt", value=default_negative_prompt, info="Used if any Guidance Scale > 1.", lines=3)
658
+ quality_slider = gr.Slider(minimum=1, maximum=10, step=1, value=6, label="Video Quality", info="If set to 10, the generated video may be too large and won't play in the Gradio preview.")
659
+ seed_input = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=42, interactive=True)
660
+ randomize_seed_checkbox = gr.Checkbox(label="Randomize seed", value=True, interactive=True)
661
+ steps_slider = gr.Slider(minimum=1, maximum=30, step=1, value=6, label="Inference Steps")
662
+ guidance_scale_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale - high noise stage", info="Values above 1 increase GPU usage and may take longer to process.")
663
+ guidance_scale_2_input = gr.Slider(minimum=0.0, maximum=10.0, step=0.5, value=1, label="Guidance Scale 2 - low noise stage")
664
+ scheduler_dropdown = gr.Dropdown(
665
+ label="Scheduler",
666
+ choices=list(SCHEDULER_MAP.keys()),
667
+ value="UniPCMultistep",
668
+ info="Select a custom scheduler."
669
+ )
670
+ flow_shift_slider = gr.Slider(minimum=0.5, maximum=15.0, step=0.1, value=3.0, label="Flow Shift")
671
+ lora_dropdown = gr.Dropdown(choices=lora_loader.get_lora_choices(), label="LoRA (NSFW)", multiselect=True, info="Select scenario LoRAs")
672
+ play_result_video = gr.Checkbox(label="Display result", value=True, interactive=True)
673
+
674
+ generate_button = gr.Button("Generate Video", variant="primary")
675
+
676
+ with gr.Column():
677
+ # ASSIGNED elem_id="generated-video" so JS can find it
678
+ video_output = gr.Video(label="Generated Video", autoplay=True, sources=["upload"], buttons=["download", "share"], interactive=True, elem_id="generated-video")
679
+
680
+ # --- Frame Grabbing UI ---
681
+ with gr.Row():
682
+ grab_frame_btn = gr.Button("📸 Use Current Frame as Input", variant="secondary")
683
+ timestamp_box = gr.Number(value=0, label="Timestamp", visible=True, elem_id="hidden-timestamp")
684
+ # -------------------------
685
+
686
+ file_output = gr.File(label="Download Video")
687
+
688
+ ui_inputs = [
689
+ input_image_component, last_image_component, prompt_input, steps_slider,
690
+ negative_prompt_input, duration_seconds_input,
691
+ guidance_scale_input, guidance_scale_2_input, seed_input, randomize_seed_checkbox,
692
+ quality_slider, scheduler_dropdown, flow_shift_slider, frame_multi,
693
+ safe_mode_checkbox,
694
+ lora_dropdown,
695
+ play_result_video
696
+ ]
697
+
698
+ generate_button.click(
699
+ fn=generate_video,
700
+ inputs=ui_inputs,
701
+ outputs=[video_output, file_output, seed_input]
702
+ )
703
+
704
+ # --- Frame Grabbing Events ---
705
+ # 1. Click button -> JS runs -> puts time in hidden number box
706
+ grab_frame_btn.click(
707
+ fn=None,
708
+ inputs=None,
709
+ outputs=[timestamp_box],
710
+ js=get_timestamp_js
711
+ )
712
+
713
+ # 2. Hidden number box changes -> Python runs -> puts frame in Input Image
714
+ timestamp_box.change(
715
+ fn=extract_frame,
716
+ inputs=[video_output, timestamp_box],
717
+ outputs=[input_image_component]
718
+ )
719
+
720
+ if __name__ == "__main__":
721
+ demo.queue().launch(
722
+ mcp_server=True,
723
+ css=CSS,
724
+ show_error=True,
725
+ )
lora_loader.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LoRA Loader for WAN 2.2 I2V - multi-repo.
3
+ Baseline: lkzd7/WAN2.2_LoraSet_NSFW. Plus curated I2V act/helper LoRAs from
4
+ Lythiga/WAN2.2_I2V_Lora, rolechar/rc_wan2.2_i2v_loraset_nsfw_private,
5
+ lopi999/Wan2.2-I2V_General-NSFW-LoRA. T2V / lightning / furry / dance / male-char filtered out.
6
+ """
7
+ import os
8
+ import urllib.parse
9
+ import re
10
+ from huggingface_hub import hf_hub_download
11
+
12
+ LORA_REPO = "lkzd7/WAN2.2_LoraSet_NSFW"
13
+ HF_TOKEN = os.environ.get("HF_TOKEN") # authenticated downloads (covers private repos)
14
+
15
+ # Pinned commit hashes (2026-07-19) — protection from breaking upstream changes
16
+ PINNED_REVISIONS = {
17
+ "lkzd7/WAN2.2_LoraSet_NSFW": "ccfa867ce4b1",
18
+ "Lythiga/WAN2.2_I2V_Lora": "67ef542e6faa",
19
+ "rolechar/rc_wan2.2_i2v_loraset_nsfw_private": "4855a2f216ac",
20
+ "lopi999/Wan2.2-I2V_General-NSFW-LoRA": "aeef17d7fa51",
21
+ }
22
+
23
+ LORA_FILES = [
24
+ "Blink_Squatting_Cowgirl_Position_I2V_HIGH.safetensors",
25
+ "Blink_Squatting_Cowgirl_Position_I2V_LOW.safetensors",
26
+ "PENISLORA_22_i2v_HIGH_e320.safetensors",
27
+ "PENISLORA_22_i2v_LOW_e496.safetensors",
28
+ "Pornmaster_wan 2.2_14b_I2V_bukkake_v1.4_high_noise.safetensors",
29
+ "Pornmaster_wan 2.2_14b_I2V_bukkake_v1.4_low_noise.safetensors",
30
+ "W22_Multiscene_Photoshoot_Softcore_i2v_HN.safetensors",
31
+ "W22_Multiscene_Photoshoot_Softcore_i2v_LN.safetensors",
32
+ "WAN-2.2-I2V-Double-Blowjob-HIGH-v1.safetensors",
33
+ "WAN-2.2-I2V-Double-Blowjob-LOW-v1.safetensors",
34
+ "WAN-2.2-I2V-HandjobBlowjobCombo-HIGH-v1.safetensors",
35
+ "WAN-2.2-I2V-HandjobBlowjobCombo-LOW-v1.safetensors",
36
+ "WAN-2.2-I2V-SensualTeasingBlowjob-HIGH-v1.safetensors",
37
+ "WAN-2.2-I2V-SensualTeasingBlowjob-LOW-v1.safetensors",
38
+ "iGOON_Blink_Blowjob_I2V_HIGH.safetensors",
39
+ "iGOON_Blink_Blowjob_I2V_LOW.safetensors",
40
+ "iGoon - Blink_Front_Doggystyle_I2V_HIGH.safetensors",
41
+ "iGoon - Blink_Front_Doggystyle_I2V_LOW.safetensors",
42
+ "iGoon - Blink_Missionary_I2V_HIGH.safetensors",
43
+ "iGoon - Blink_Missionary_I2V_LOW v2.safetensors",
44
+ "iGoon - Blink_Missionary_I2V_LOW.safetensors",
45
+ "iGoon%20-%20Blink_Back_Doggystyle_HIGH.safetensors",
46
+ "iGoon%20-%20Blink_Back_Doggystyle_LOW.safetensors",
47
+ "iGoon%20-%20Blink_Facial_I2V_HIGH.safetensors",
48
+ "iGoon%20-%20Blink_Facial_I2V_LOW.safetensors",
49
+ "iGoon_Blink_Missionary_I2V_HIGH v2.safetensors",
50
+ "iGoon_Blink_Titjob_I2V_HIGH.safetensors",
51
+ "iGoon_Blink_Titjob_I2V_LOW.safetensors",
52
+ "lips-bj_high_noise.safetensors",
53
+ "lips-bj_low_noise.safetensors",
54
+ "mql_casting_sex_doggy_kneel_diagonally_behind_vagina_wan22_i2v_v1_high_noise.safetensors",
55
+ "mql_casting_sex_doggy_kneel_diagonally_behind_vagina_wan22_i2v_v1_low_noise.safetensors",
56
+ "mql_casting_sex_reverse_cowgirl_lie_front_vagina_wan22_i2v_v1_high_noise.safetensors",
57
+ "mql_casting_sex_reverse_cowgirl_lie_front_vagina_wan22_i2v_v1_low_noise.safetensors",
58
+ "mql_casting_sex_spoon_wan22_i2v_v1_high_noise.safetensors",
59
+ "mql_casting_sex_spoon_wan22_i2v_v1_low_noise.safetensors",
60
+ "mql_massage_tits_wan22_i2v_v1_high_noise.safetensors",
61
+ "mql_massage_tits_wan22_i2v_v1_low_noise.safetensors",
62
+ "mql_panties_aside_wan22_i2v_v1_high_noise.safetensors",
63
+ "mql_panties_aside_wan22_i2v_v1_low_noise.safetensors",
64
+ "sfbehind_v2.1_high_noise.safetensors",
65
+ "sfbehind_v2.1_low_noise.safetensors",
66
+ "sid3l3g_transition_v2.0_H.safetensors",
67
+ "sid3l3g_transition_v2.0_L.safetensors",
68
+ "wan2.2_i2v_high_ulitmate_pussy_asshole.safetensors",
69
+ "wan2.2_i2v_low_ulitmate_pussy_asshole.safetensors",
70
+ "wan22-mouthfull-140epoc-high-k3nk.safetensors",
71
+ "wan22-mouthfull-152epoc-low-k3nk.safetensors",
72
+ ]
73
+
74
+ # group -> {"HIGH": (repo, file) | None, "LOW": (repo, file) | None}
75
+ LORA_PAIRS = {}
76
+ for f in LORA_FILES:
77
+ name = urllib.parse.unquote(f).replace(".safetensors", "")
78
+ is_high = bool(re.search(r'(high|HN|_H\b)', name, re.IGNORECASE))
79
+ is_low = bool(re.search(r'(low|LN|_L\b)', name, re.IGNORECASE))
80
+ group = re.sub(r'[\s_-]*(high|low|noise|HN|LN)([\s_-]*noise)?[\s_-]*(v?\d+(\.\d+)?)?\s*$', '', name, flags=re.IGNORECASE).strip()
81
+ group = re.sub(r'[\s_]+$', '', group)
82
+ LORA_PAIRS.setdefault(group, {"HIGH": None, "LOW": None})
83
+ if is_high:
84
+ LORA_PAIRS[group]["HIGH"] = (LORA_REPO, f)
85
+ elif is_low:
86
+ LORA_PAIRS[group]["LOW"] = (LORA_REPO, f)
87
+
88
+ L = "Lythiga/WAN2.2_I2V_Lora"
89
+ R = "rolechar/rc_wan2.2_i2v_loraset_nsfw_private"
90
+ P = "lopi999/Wan2.2-I2V_General-NSFW-LoRA"
91
+
92
+ # curated extra act/helper LoRAs (label -> (repo, high_file, low_file|None))
93
+ EXTRA = {
94
+ # --- deepthroat ---
95
+ "Ultimate Deepthroat": (L, "wan22-ultimatedeepthroat-i2v-102epoc-high-k3nk.safetensors", "wan22-ultimatedeepthroat-I2V-101epoc-low-k3nk.safetensors"),
96
+ "BBC Deepthroat": (L, "wan22-bbcdeepthroat-115epoc-high-k3nk.safetensors", "wan22-bbcdeepthroat-155epoc-low-720-k3nk.safetensors"),
97
+ "Throat V2": (L, "Wan22_ThroatV2_High.safetensors", "Wan22_ThroatV2_Low.safetensors"),
98
+ "JFJ Deepthroat": (L, "jfj-deepthroat-W22-I2V-HN.safetensors", None),
99
+ # --- blowjob extra ---
100
+ "BBC Blowjob Extreme": (L, "BBC Blowjobs Extreme_high_noise.safetensors", "BBC Blowjobs Extreme_low_noise.safetensors"),
101
+ "POV Blowjob": (L, "Pov_Blowjob_Wan2.2_high_I2V_v1.0.safetensors", None),
102
+ # --- cunnilingus ---
103
+ "Cunnilingus": (L, "wan22-cunilingus-I2V-106epoc-high.safetensors", "wan22-cunilingus-I2V-72epoc-low.safetensors"),
104
+ # --- cowgirl ---
105
+ "Reverse Cowgirl v2": (L, "wan22.r3v3rs3_c0wg1rl-14b-High-i2v_e70.safetensors", "wan22.r3v3rs3_c0wg1rl-14b-Low-i2v_e70.safetensors"),
106
+ # --- doggy / from behind ---
107
+ "Doggy Front View v2": (L, "doggy_style_sex_front_view_Wan2.2_I2V_high_v1.0.safetensors", "doggy_style_sex_front_view_Wan2.2_I2V_Low_v1.0.safetensors"),
108
+ "Fucked From Behind": (L, "derpfuckedfrombehind_000002125_high_noise.safetensors", "derpfuckedfrombehind_000002125_low_noise.safetensors"),
109
+ "Prone Bone": (L, "Pronebone_high_noise.safetensors", "Pronebone_low_noise.safetensors"),
110
+ "Reverse Suspended Congress": (L, "reverse_suspended_congress_I2V_high.safetensors", "reverse_suspended_congress_I2V_low.safetensors"),
111
+ # --- missionary ---
112
+ "POV Missionary": (L, "wan2.2_i2v_highnoise_pov_missionary_v1.0.safetensors", "wan2.2_i2v_lownoise_pov_missionary_v1.0.safetensors"),
113
+ "Mating Press": (L, "mating_press_high.safetensors", "mating_press_low.safetensors"),
114
+ # --- standing ---
115
+ "Standing Upright Sex": (L, "Upright_sex_Wan2.2_14B_TI2V_HIGH_v1.0.safetensors", "Upright_sex_Wan2.2_14B_TI2V_LOW_v1.0.safetensors"),
116
+ # --- anal / ass ---
117
+ "Oral Insertion": (L, "wan2.2-i2v-high-oral-insertion-v1.0.safetensors", "wan2.2-i2v-low-oral-insertion-v1.0.safetensors"),
118
+ # --- handjob / balls ---
119
+ "Handjob": (L, "WAN-2.2-I2V-Handjob-HIGH-v1.safetensors", "WAN-2.2-I2V-Handjob-LOW-v1.safetensors"),
120
+ "Balls Sucking": (L, "WAN-2.2-I2V_Balls_sucking_High_noise.safetensors", "WAN-2.2-I2V_Balls_sucking_Low_noise.safetensors"),
121
+ # --- fingering ---
122
+ "Fingering": (R, "Sensual_fingering_v1_high_noise.safetensors", "Sensual_fingering_v1_low_noise.safetensors"),
123
+ # --- cumshot / creampie ---
124
+ "Facesplash Cumshot": (L, "wan22-f4c3spl4sh-100epoc-high-k3nk.safetensors", "wan22-f4c3spl4sh-154epoc-low-k3nk.safetensors"),
125
+ "Creampie CRM": (L, "Creampie CRM-FULL-EPOCH-80-HIGH.safetensors", "Creampie CRM-FULL-EPOCH-80-LOW.safetensors"),
126
+ "Pornmaster Creampie": (L, "Pornmaster_wan 2.2_14b_I2V_Creampie_v1_high_noise.safetensors", "Pornmaster_wan 2.2_14b_I2V_Creampie_v1_low_noise.safetensors"),
127
+ "Cum (generic)": (L, "Wan22_Cum_high_noise_1.V1.safetensors", "Wan22_Cum_low_noise_1.V1.safetensors"),
128
+ # --- fetish / expression ---
129
+ "French Kiss": (L, "WAN2.2-FrenchKiss_HighNoise.safetensors", "WAN2.2-FrenchKiss_LowNoise.safetensors"),
130
+ "Twerking": (L, "Twerking_I2V_high_noise.safetensors", "Twerking_I2V_low_noise.safetensors"),
131
+ "Ahegao Drooling": (R, "middlefinger_drooling_ahegao_high.safetensors", "middlefinger_drooling_ahegao_low.safetensors"),
132
+ # --- helpers: size / penetration / anatomy / camera / booster ---
133
+ "General NSFW Booster": (P, "NSFW-22-H-e8.safetensors", "NSFW-22-L-e8.safetensors"),
134
+ "Penetration Insert": (L, "PenInsert_high_noise.safetensors", "PenInsert_low_noise.safetensors"),
135
+ "Biggest Cock (size)": (L, "WAN-2.2-I2V_BiggestCock_high_noise_V1.safetensors", "WAN-2.2-I2V_BiggestCock_low_noise_V1.safetensors"),
136
+ "Doggy Slider": (L, "I2V_doggyslider_high.safetensors", "I2V_doggyslider_low.safetensors"),
137
+ "Pussy Helper": (R, "wan2.2_i2v_ulitmate_pussy_helper_high.safetensors", "wan2.2_i2v_ulitmate_pussy_helper_low.safetensors"),
138
+ "FOV Slider (camera)": (L, "wan2.2-i2v-high-sex-fov-slider-v1.0.safetensors", "wan2.2-i2v-low-sex-fov-slider-v1.0.safetensors"),
139
+ "Smashcut (camera)": (R, "wan2.2-i2v-sex-smashcut-v1.0-high.safetensors", "wan2.2-i2v-sex-smashcut-v1.0-low.safetensors"),
140
+ }
141
+ for label, (repo, hi, lo) in EXTRA.items():
142
+ LORA_PAIRS.setdefault(label, {"HIGH": None, "LOW": None})
143
+ if hi:
144
+ LORA_PAIRS[label]["HIGH"] = (repo, hi)
145
+ if lo:
146
+ LORA_PAIRS[label]["LOW"] = (repo, lo)
147
+
148
+
149
+ def get_lora_choices():
150
+ choices = []
151
+ for group in sorted(LORA_PAIRS.keys()):
152
+ p = LORA_PAIRS[group]
153
+ if p["HIGH"] and p["LOW"]:
154
+ choices.append(group)
155
+ elif p["HIGH"]:
156
+ choices.append(f"{group} (HIGH only)")
157
+ elif p["LOW"]:
158
+ choices.append(f"{group} (LOW only)")
159
+ return choices
160
+
161
+
162
+ def download_lora(group_name):
163
+ if not group_name:
164
+ return None, None
165
+ clean_name = re.sub(r'\s*\(HIGH only\)|\s*\(LOW only\)', '', group_name)
166
+ if clean_name not in LORA_PAIRS:
167
+ return None, None
168
+ pair = LORA_PAIRS[clean_name]
169
+ high_path, low_path = None, None
170
+ if pair["HIGH"]:
171
+ repo, fn = pair["HIGH"]
172
+ high_path = hf_hub_download(repo, fn, token=HF_TOKEN, revision=PINNED_REVISIONS.get(repo))
173
+ if pair["LOW"]:
174
+ repo, fn = pair["LOW"]
175
+ low_path = hf_hub_download(repo, fn, token=HF_TOKEN, revision=PINNED_REVISIONS.get(repo))
176
+ return high_path, low_path
177
+
178
+
179
+ def load_lora_to_pipe(pipe, group_name, adapter_name="lora"):
180
+ high_path, low_path = download_lora(group_name)
181
+ if high_path and low_path:
182
+ pipe.load_lora_weights(high_path, adapter_name=f"{adapter_name}_high")
183
+ pipe.load_lora_weights(low_path, adapter_name=f"{adapter_name}_low")
184
+ print(f"Loaded LoRA pair: {group_name}")
185
+ return True
186
+ elif high_path:
187
+ pipe.load_lora_weights(high_path, adapter_name=adapter_name)
188
+ print(f"Loaded LoRA: {group_name}")
189
+ return True
190
+ elif low_path:
191
+ pipe.load_lora_weights(low_path, adapter_name=adapter_name)
192
+ print(f"Loaded LoRA (low): {group_name}")
193
+ return True
194
+ return False
195
+
196
+
197
+ def unload_lora(pipe):
198
+ try:
199
+ pipe.unload_lora_weights()
200
+ except:
201
+ pass
model/loss.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import numpy as np
3
+ import torch.nn as nn
4
+ import torch.nn.functional as F
5
+ import torchvision.models as models
6
+
7
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
8
+
9
+
10
+ class EPE(nn.Module):
11
+ def __init__(self):
12
+ super(EPE, self).__init__()
13
+
14
+ def forward(self, flow, gt, loss_mask):
15
+ loss_map = (flow - gt.detach()) ** 2
16
+ loss_map = (loss_map.sum(1, True) + 1e-6) ** 0.5
17
+ return (loss_map * loss_mask)
18
+
19
+
20
+ class Ternary(nn.Module):
21
+ def __init__(self):
22
+ super(Ternary, self).__init__()
23
+ patch_size = 7
24
+ out_channels = patch_size * patch_size
25
+ self.w = np.eye(out_channels).reshape(
26
+ (patch_size, patch_size, 1, out_channels))
27
+ self.w = np.transpose(self.w, (3, 2, 0, 1))
28
+ self.w = torch.tensor(self.w).float().to(device)
29
+
30
+ def transform(self, img):
31
+ patches = F.conv2d(img, self.w, padding=3, bias=None)
32
+ transf = patches - img
33
+ transf_norm = transf / torch.sqrt(0.81 + transf**2)
34
+ return transf_norm
35
+
36
+ def rgb2gray(self, rgb):
37
+ r, g, b = rgb[:, 0:1, :, :], rgb[:, 1:2, :, :], rgb[:, 2:3, :, :]
38
+ gray = 0.2989 * r + 0.5870 * g + 0.1140 * b
39
+ return gray
40
+
41
+ def hamming(self, t1, t2):
42
+ dist = (t1 - t2) ** 2
43
+ dist_norm = torch.mean(dist / (0.1 + dist), 1, True)
44
+ return dist_norm
45
+
46
+ def valid_mask(self, t, padding):
47
+ n, _, h, w = t.size()
48
+ inner = torch.ones(n, 1, h - 2 * padding, w - 2 * padding).type_as(t)
49
+ mask = F.pad(inner, [padding] * 4)
50
+ return mask
51
+
52
+ def forward(self, img0, img1):
53
+ img0 = self.transform(self.rgb2gray(img0))
54
+ img1 = self.transform(self.rgb2gray(img1))
55
+ return self.hamming(img0, img1) * self.valid_mask(img0, 1)
56
+
57
+
58
+ class SOBEL(nn.Module):
59
+ def __init__(self):
60
+ super(SOBEL, self).__init__()
61
+ self.kernelX = torch.tensor([
62
+ [1, 0, -1],
63
+ [2, 0, -2],
64
+ [1, 0, -1],
65
+ ]).float()
66
+ self.kernelY = self.kernelX.clone().T
67
+ self.kernelX = self.kernelX.unsqueeze(0).unsqueeze(0).to(device)
68
+ self.kernelY = self.kernelY.unsqueeze(0).unsqueeze(0).to(device)
69
+
70
+ def forward(self, pred, gt):
71
+ N, C, H, W = pred.shape[0], pred.shape[1], pred.shape[2], pred.shape[3]
72
+ img_stack = torch.cat(
73
+ [pred.reshape(N*C, 1, H, W), gt.reshape(N*C, 1, H, W)], 0)
74
+ sobel_stack_x = F.conv2d(img_stack, self.kernelX, padding=1)
75
+ sobel_stack_y = F.conv2d(img_stack, self.kernelY, padding=1)
76
+ pred_X, gt_X = sobel_stack_x[:N*C], sobel_stack_x[N*C:]
77
+ pred_Y, gt_Y = sobel_stack_y[:N*C], sobel_stack_y[N*C:]
78
+
79
+ L1X, L1Y = torch.abs(pred_X-gt_X), torch.abs(pred_Y-gt_Y)
80
+ loss = (L1X+L1Y)
81
+ return loss
82
+
83
+ class MeanShift(nn.Conv2d):
84
+ def __init__(self, data_mean, data_std, data_range=1, norm=True):
85
+ c = len(data_mean)
86
+ super(MeanShift, self).__init__(c, c, kernel_size=1)
87
+ std = torch.Tensor(data_std)
88
+ self.weight.data = torch.eye(c).view(c, c, 1, 1)
89
+ if norm:
90
+ self.weight.data.div_(std.view(c, 1, 1, 1))
91
+ self.bias.data = -1 * data_range * torch.Tensor(data_mean)
92
+ self.bias.data.div_(std)
93
+ else:
94
+ self.weight.data.mul_(std.view(c, 1, 1, 1))
95
+ self.bias.data = data_range * torch.Tensor(data_mean)
96
+ self.requires_grad = False
97
+
98
+ class VGGPerceptualLoss(torch.nn.Module):
99
+ def __init__(self, rank=0):
100
+ super(VGGPerceptualLoss, self).__init__()
101
+ blocks = []
102
+ pretrained = True
103
+ self.vgg_pretrained_features = models.vgg19(pretrained=pretrained).features
104
+ self.normalize = MeanShift([0.485, 0.456, 0.406], [0.229, 0.224, 0.225], norm=True).cuda()
105
+ for param in self.parameters():
106
+ param.requires_grad = False
107
+
108
+ def forward(self, X, Y, indices=None):
109
+ X = self.normalize(X)
110
+ Y = self.normalize(Y)
111
+ indices = [2, 7, 12, 21, 30]
112
+ weights = [1.0/2.6, 1.0/4.8, 1.0/3.7, 1.0/5.6, 10/1.5]
113
+ k = 0
114
+ loss = 0
115
+ for i in range(indices[-1]):
116
+ X = self.vgg_pretrained_features[i](X)
117
+ Y = self.vgg_pretrained_features[i](Y)
118
+ if (i+1) in indices:
119
+ loss += weights[k] * (X - Y.detach()).abs().mean() * 0.1
120
+ k += 1
121
+ return loss
122
+
123
+ if __name__ == '__main__':
124
+ img0 = torch.zeros(3, 3, 256, 256).float().to(device)
125
+ img1 = torch.tensor(np.random.normal(
126
+ 0, 1, (3, 3, 256, 256))).float().to(device)
127
+ ternary_loss = Ternary()
128
+ print(ternary_loss(img0, img1).shape)
model/pytorch_msssim/__init__.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn.functional as F
3
+ from math import exp
4
+ import numpy as np
5
+
6
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
7
+
8
+ def gaussian(window_size, sigma):
9
+ gauss = torch.Tensor([exp(-(x - window_size//2)**2/float(2*sigma**2)) for x in range(window_size)])
10
+ return gauss/gauss.sum()
11
+
12
+
13
+ def create_window(window_size, channel=1):
14
+ _1D_window = gaussian(window_size, 1.5).unsqueeze(1)
15
+ _2D_window = _1D_window.mm(_1D_window.t()).float().unsqueeze(0).unsqueeze(0).to(device)
16
+ window = _2D_window.expand(channel, 1, window_size, window_size).contiguous()
17
+ return window
18
+
19
+ def create_window_3d(window_size, channel=1):
20
+ _1D_window = gaussian(window_size, 1.5).unsqueeze(1)
21
+ _2D_window = _1D_window.mm(_1D_window.t())
22
+ _3D_window = _2D_window.unsqueeze(2) @ (_1D_window.t())
23
+ window = _3D_window.expand(1, channel, window_size, window_size, window_size).contiguous().to(device)
24
+ return window
25
+
26
+
27
+ def ssim(img1, img2, window_size=11, window=None, size_average=True, full=False, val_range=None):
28
+ # Value range can be different from 255. Other common ranges are 1 (sigmoid) and 2 (tanh).
29
+ if val_range is None:
30
+ if torch.max(img1) > 128:
31
+ max_val = 255
32
+ else:
33
+ max_val = 1
34
+
35
+ if torch.min(img1) < -0.5:
36
+ min_val = -1
37
+ else:
38
+ min_val = 0
39
+ L = max_val - min_val
40
+ else:
41
+ L = val_range
42
+
43
+ padd = 0
44
+ (_, channel, height, width) = img1.size()
45
+ if window is None:
46
+ real_size = min(window_size, height, width)
47
+ window = create_window(real_size, channel=channel).to(img1.device).type_as(img1)
48
+
49
+ mu1 = F.conv2d(F.pad(img1, (5, 5, 5, 5), mode='replicate'), window, padding=padd, groups=channel)
50
+ mu2 = F.conv2d(F.pad(img2, (5, 5, 5, 5), mode='replicate'), window, padding=padd, groups=channel)
51
+
52
+ mu1_sq = mu1.pow(2)
53
+ mu2_sq = mu2.pow(2)
54
+ mu1_mu2 = mu1 * mu2
55
+
56
+ sigma1_sq = F.conv2d(F.pad(img1 * img1, (5, 5, 5, 5), 'replicate'), window, padding=padd, groups=channel) - mu1_sq
57
+ sigma2_sq = F.conv2d(F.pad(img2 * img2, (5, 5, 5, 5), 'replicate'), window, padding=padd, groups=channel) - mu2_sq
58
+ sigma12 = F.conv2d(F.pad(img1 * img2, (5, 5, 5, 5), 'replicate'), window, padding=padd, groups=channel) - mu1_mu2
59
+
60
+ C1 = (0.01 * L) ** 2
61
+ C2 = (0.03 * L) ** 2
62
+
63
+ v1 = 2.0 * sigma12 + C2
64
+ v2 = sigma1_sq + sigma2_sq + C2
65
+ cs = torch.mean(v1 / v2) # contrast sensitivity
66
+
67
+ ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2)
68
+
69
+ if size_average:
70
+ ret = ssim_map.mean()
71
+ else:
72
+ ret = ssim_map.mean(1).mean(1).mean(1)
73
+
74
+ if full:
75
+ return ret, cs
76
+ return ret
77
+
78
+
79
+ def ssim_matlab(img1, img2, window_size=11, window=None, size_average=True, full=False, val_range=None):
80
+ # Value range can be different from 255. Other common ranges are 1 (sigmoid) and 2 (tanh).
81
+ if val_range is None:
82
+ if torch.max(img1) > 128:
83
+ max_val = 255
84
+ else:
85
+ max_val = 1
86
+
87
+ if torch.min(img1) < -0.5:
88
+ min_val = -1
89
+ else:
90
+ min_val = 0
91
+ L = max_val - min_val
92
+ else:
93
+ L = val_range
94
+
95
+ padd = 0
96
+ (_, _, height, width) = img1.size()
97
+ if window is None:
98
+ real_size = min(window_size, height, width)
99
+ window = create_window_3d(real_size, channel=1).to(img1.device).type_as(img1)
100
+ # Channel is set to 1 since we consider color images as volumetric images
101
+
102
+ img1 = img1.unsqueeze(1)
103
+ img2 = img2.unsqueeze(1)
104
+
105
+ mu1 = F.conv3d(F.pad(img1, (5, 5, 5, 5, 5, 5), mode='replicate'), window, padding=padd, groups=1)
106
+ mu2 = F.conv3d(F.pad(img2, (5, 5, 5, 5, 5, 5), mode='replicate'), window, padding=padd, groups=1)
107
+
108
+ mu1_sq = mu1.pow(2)
109
+ mu2_sq = mu2.pow(2)
110
+ mu1_mu2 = mu1 * mu2
111
+
112
+ sigma1_sq = F.conv3d(F.pad(img1 * img1, (5, 5, 5, 5, 5, 5), 'replicate'), window, padding=padd, groups=1) - mu1_sq
113
+ sigma2_sq = F.conv3d(F.pad(img2 * img2, (5, 5, 5, 5, 5, 5), 'replicate'), window, padding=padd, groups=1) - mu2_sq
114
+ sigma12 = F.conv3d(F.pad(img1 * img2, (5, 5, 5, 5, 5, 5), 'replicate'), window, padding=padd, groups=1) - mu1_mu2
115
+
116
+ C1 = (0.01 * L) ** 2
117
+ C2 = (0.03 * L) ** 2
118
+
119
+ v1 = 2.0 * sigma12 + C2
120
+ v2 = sigma1_sq + sigma2_sq + C2
121
+ cs = torch.mean(v1 / v2) # contrast sensitivity
122
+
123
+ ssim_map = ((2 * mu1_mu2 + C1) * v1) / ((mu1_sq + mu2_sq + C1) * v2)
124
+
125
+ if size_average:
126
+ ret = ssim_map.mean()
127
+ else:
128
+ ret = ssim_map.mean(1).mean(1).mean(1)
129
+
130
+ if full:
131
+ return ret, cs
132
+ return ret
133
+
134
+
135
+ def msssim(img1, img2, window_size=11, size_average=True, val_range=None, normalize=False):
136
+ device = img1.device
137
+ weights = torch.FloatTensor([0.0448, 0.2856, 0.3001, 0.2363, 0.1333]).to(device).type_as(img1)
138
+ levels = weights.size()[0]
139
+ mssim = []
140
+ mcs = []
141
+ for _ in range(levels):
142
+ sim, cs = ssim(img1, img2, window_size=window_size, size_average=size_average, full=True, val_range=val_range)
143
+ mssim.append(sim)
144
+ mcs.append(cs)
145
+
146
+ img1 = F.avg_pool2d(img1, (2, 2))
147
+ img2 = F.avg_pool2d(img2, (2, 2))
148
+
149
+ mssim = torch.stack(mssim)
150
+ mcs = torch.stack(mcs)
151
+
152
+ # Normalize (to avoid NaNs during training unstable models, not compliant with original definition)
153
+ if normalize:
154
+ mssim = (mssim + 1) / 2
155
+ mcs = (mcs + 1) / 2
156
+
157
+ pow1 = mcs ** weights
158
+ pow2 = mssim ** weights
159
+ # From Matlab implementation https://ece.uwaterloo.ca/~z70wang/research/iwssim/
160
+ output = torch.prod(pow1[:-1] * pow2[-1])
161
+ return output
162
+
163
+
164
+ # Classes to re-use window
165
+ class SSIM(torch.nn.Module):
166
+ def __init__(self, window_size=11, size_average=True, val_range=None):
167
+ super(SSIM, self).__init__()
168
+ self.window_size = window_size
169
+ self.size_average = size_average
170
+ self.val_range = val_range
171
+
172
+ # Assume 3 channel for SSIM
173
+ self.channel = 3
174
+ self.window = create_window(window_size, channel=self.channel)
175
+
176
+ def forward(self, img1, img2):
177
+ (_, channel, _, _) = img1.size()
178
+
179
+ if channel == self.channel and self.window.dtype == img1.dtype:
180
+ window = self.window
181
+ else:
182
+ window = create_window(self.window_size, channel).to(img1.device).type(img1.dtype)
183
+ self.window = window
184
+ self.channel = channel
185
+
186
+ _ssim = ssim(img1, img2, window=window, window_size=self.window_size, size_average=self.size_average)
187
+ dssim = (1 - _ssim) / 2
188
+ return dssim
189
+
190
+ class MSSSIM(torch.nn.Module):
191
+ def __init__(self, window_size=11, size_average=True, channel=3):
192
+ super(MSSSIM, self).__init__()
193
+ self.window_size = window_size
194
+ self.size_average = size_average
195
+ self.channel = channel
196
+
197
+ def forward(self, img1, img2):
198
+ return msssim(img1, img2, window_size=self.window_size, size_average=self.size_average)
model/warplayer.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
5
+ backwarp_tenGrid = {}
6
+
7
+
8
+ def warp(tenInput, tenFlow):
9
+ k = (str(tenFlow.device), str(tenFlow.size()))
10
+ if k not in backwarp_tenGrid:
11
+ tenHorizontal = torch.linspace(-1.0, 1.0, tenFlow.shape[3], device=tenFlow.device).view(
12
+ 1, 1, 1, tenFlow.shape[3]).expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1)
13
+ tenVertical = torch.linspace(-1.0, 1.0, tenFlow.shape[2], device=tenFlow.device).view(
14
+ 1, 1, tenFlow.shape[2], 1).expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3])
15
+ backwarp_tenGrid[k] = torch.cat(
16
+ [tenHorizontal, tenVertical], 1).to(tenFlow.device)
17
+
18
+ tenFlow = torch.cat([tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0),
19
+ tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0)], 1)
20
+
21
+ grid = backwarp_tenGrid[k].type_as(tenFlow)
22
+
23
+ g = (grid + tenFlow).permute(0, 2, 3, 1)
24
+ return torch.nn.functional.grid_sample(input=tenInput, grid=g, mode='bilinear', padding_mode='border', align_corners=True)
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ diffusers==0.38.0
2
+ transformers==4.57.6
3
+ accelerate===1.13.0
4
+ safetensors
5
+ sentencepiece
6
+ peft==0.19.1
7
+ ftfy
8
+ imageio
9
+ imageio-ffmpeg
10
+ opencv-python
11
+ torchao==0.17.0
12
+
13
+ numpy>=1.16, <=1.23.5
14
+ # tqdm>=4.35.0
15
+ # sk-video>=1.1.10
16
+ # opencv-python>=4.1.2
17
+ # moviepy>=1.0.3
18
+ torch==2.11.0
19
+ torchvision==0.26.0