I2V-VIP / rife_interp.py
dayona's picture
migrate rife_interp to native urllib.request to fix Gradio thread deadlock
f4399f0
Raw
History Blame Contribute Delete
10.8 kB
import os
import gc
import subprocess
import numpy as np
import torch
from torch.nn import functional as F
from tqdm import tqdm
def is_cuda_usable():
if not torch.cuda.is_available():
return False
try:
_ = torch.zeros(1, device="cuda")
return True
except Exception:
return False
def clear_vram():
gc.collect()
if is_cuda_usable():
try:
torch.cuda.empty_cache()
except Exception:
pass
def create_classic_boomerang_loop(frames_np):
"""
Creates classic 100% real-speed full boomerang loop (forward + reverse).
Reverts to true original speed without artificial slow blending.
"""
if frames_np is None or len(frames_np) < 4:
return frames_np
is_list = isinstance(frames_np, list)
forward = list(frames_np) if is_list else [f for f in frames_np]
# Exclude boundary duplicates to keep smooth motion flow
reversed_frames = list(forward[::-1])[1:-1]
result = forward + reversed_frames
return result if is_list else np.array(result)
def create_ending_boomerang_loop(frames_np, tail_ratio=0.4, min_tail_frames=24):
"""
Creates ending-only boomerang loop: plays full video forward normally,
then boomerangs only the last 1.5-2.0s tail frames at 100% real speed.
"""
if frames_np is None or len(frames_np) < 6:
return frames_np
is_list = isinstance(frames_np, list)
forward = list(frames_np) if is_list else [f for f in frames_np]
n_frames = len(forward)
# Calculate tail frame count (last ~1.5s to 2.0s based on total frames)
tail_count = max(min_tail_frames, int(n_frames * tail_ratio))
tail_count = min(n_frames - 2, tail_count)
tail_frames = forward[-tail_count:]
reversed_tail = list(tail_frames[::-1])[1:-1]
result = forward + reversed_tail
return result if is_list else np.array(result)
def create_dynamic_boomerang_loop(frames_np, blend_frames=3):
return create_classic_boomerang_loop(frames_np)
def create_adaptive_speed_ramping(frames_np, multiplier=2):
"""
Applies non-linear motion-compensated speed ramping (Ease-In / Ease-Out curve).
Preserves 100% real-time motion speed during fast action/middle segments,
while smoothly easing start and end keyframes to extend duration naturally.
"""
if frames_np is None or len(frames_np) < 6:
return frames_np
is_list = isinstance(frames_np, list)
forward = list(frames_np) if is_list else [f for f in frames_np]
N = len(forward)
target_count = int((N - 1) * multiplier + 1)
out_frames = []
for k in range(target_count):
s = k / float(target_count - 1)
# Cubic Smoothstep Easing: 3*s^2 - 2*s^3
eased_s = s * s * (3.0 - 2.0 * s)
pos = eased_s * (N - 1)
idx0 = int(pos)
idx1 = min(N - 1, idx0 + 1)
alpha = pos - idx0
if alpha < 0.01 or idx0 == idx1:
out_frames.append(forward[idx0])
else:
f0 = np.array(forward[idx0], dtype=np.float32)
f1 = np.array(forward[idx1], dtype=np.float32)
blended = (1.0 - alpha) * f0 + alpha * f1
out_frames.append(blended.astype(np.uint8) if forward[0].dtype == np.uint8 else blended)
return out_frames if is_list else np.array(out_frames)
# Download and initialize RIFE Model
if not os.path.exists("RIFEv4.26_0921.zip"):
print("Downloading RIFE Model...")
subprocess.run([
"wget", "-q",
"https://huggingface.co/thornmaze/RIFE/resolve/main/RIFEv4.26_0921.zip",
"-O", "RIFEv4.26_0921.zip"
], check=True)
subprocess.run(["unzip", "-o", "RIFEv4.26_0921.zip"], check=True)
from train_log.RIFE_HDv3 import Model
rife_model = Model()
rife_model.load_model("train_log", -1)
rife_model.eval()
@torch.no_grad()
def interpolate_bits(frames_np, multiplier=2, scale=1.0):
if isinstance(frames_np, list):
T = len(frames_np)
H, W, C = frames_np[0].shape
else:
T, H, W, C = frames_np.shape
if multiplier < 2:
if isinstance(frames_np, np.ndarray):
return list(frames_np)
return frames_np
n_interp = multiplier - 1
tmp = max(128, int(128 / scale))
ph = ((H - 1) // tmp + 1) * tmp
pw = ((W - 1) // tmp + 1) * tmp
padding = (0, pw - W, 0, ph - H)
use_cuda = is_cuda_usable()
curr_device = torch.device("cuda" if use_cuda else "cpu")
try:
if hasattr(rife_model, "flownet") and rife_model.flownet is not None:
rife_model.flownet = rife_model.flownet.to(curr_device)
if use_cuda:
rife_model.flownet = rife_model.flownet.half()
else:
rife_model.flownet = rife_model.flownet.float()
except Exception as e:
print(f"RIFE model device placement notice: {e}")
def to_tensor(frame_np):
t = torch.from_numpy(frame_np).to(curr_device)
t = t.permute(2, 0, 1).unsqueeze(0)
if curr_device.type == "cuda":
return F.pad(t, padding).half()
return F.pad(t, padding).float()
def from_tensor(tensor):
t = tensor[0, :, :H, :W]
t = t.permute(1, 2, 0)
return t.float().cpu().numpy()
def make_inference(I0, I1, n):
if rife_model.version >= 3.9:
res = []
for i in range(n):
res.append(rife_model.inference(I0, I1, (i+1) * 1. / (n+1), scale))
return res
else:
middle = rife_model.inference(I0, I1, scale)
if n == 1:
return [middle]
first_half = make_inference(I0, middle, n=n//2)
second_half = make_inference(middle, I1, n=n//2)
if n % 2:
return [*first_half, middle, *second_half]
else:
return [*first_half, *second_half]
output_frames = []
I1 = to_tensor(frames_np[0])
total_steps = T - 1
with tqdm(total=total_steps, desc="Interpolating", unit="frame") as pbar:
for i in range(total_steps):
I0 = I1
output_frames.append(from_tensor(I0))
I1 = to_tensor(frames_np[i+1])
mid_tensors = make_inference(I0, I1, n_interp)
for mid in mid_tensors:
output_frames.append(from_tensor(mid))
if (i + 1) % 50 == 0:
pbar.update(50)
pbar.update(total_steps % 50)
output_frames.append(from_tensor(I1))
del I0, I1, mid_tensors
if curr_device.type == "cuda" and is_cuda_usable():
try:
torch.cuda.empty_cache()
except Exception:
pass
return output_frames
def call_sulphur_rife_api(video_path, multiplier=2, slow_motion=False, upscale=True, enhance_face=False, server_url=None, progress=None):
"""
Calls Sulphur AI VIP RIFE API (/api/v1/rife-extend) with support for RIFE frame interpolation,
Real-ESRGAN 1080p HD Super-Resolution, and Temporal Face Sharpening.
Uses standard library urllib.request for ultra-fast, deadlock-free HTTP transfers.
Returns path to output MP4 video file.
"""
import os
import json
import tempfile
import urllib.request
import config
if not video_path or not os.path.exists(video_path):
return None
target_url = server_url or config.SULPHUR_API_URL or os.environ.get("SULPHUR_API_URL", "http://localhost:6666")
if not target_url or not str(target_url).strip():
return None
clean_url = str(target_url).strip().rstrip("/")
endpoint = f"{clean_url}/api/v1/rife-extend"
try:
print(f"🌐 Calling Sulphur AI VIP RIFE API at {endpoint} (multiplier={multiplier}, slow_motion={slow_motion}, upscale={upscale}, enhance_face={enhance_face})...")
if progress:
progress(0.91, desc="🌐 Uploading video to VIP Remote GPU Server...")
with open(video_path, "rb") as vf:
file_bytes = vf.read()
boundary = "----SulphurAIRIFEBoundary7MA4YWxkTrZu0gW"
body = []
body.append(f"--{boundary}\r\nContent-Disposition: form-data; name=\"video\"; filename=\"{os.path.basename(video_path)}\"\r\nContent-Type: video/mp4\r\n\r\n".encode("utf-8"))
body.append(file_bytes)
body.append(b"\r\n")
params = [
("multiplier", str(int(multiplier))),
("slow_motion", "true" if slow_motion else "false"),
("upscale", "true" if upscale else "false"),
("enhance_face", "true" if enhance_face else "false")
]
for k, v in params:
body.append(f"--{boundary}\r\nContent-Disposition: form-data; name=\"{k}\"\r\n\r\n{v}\r\n".encode("utf-8"))
body.append(f"--{boundary}--\r\n".encode("utf-8"))
payload = b"".join(body)
req = urllib.request.Request(
endpoint,
data=payload,
headers={
"Content-Type": f"multipart/form-data; boundary={boundary}",
"User-Agent": "Mozilla/5.0 (SulphurAI-Space-Client)"
}
)
if progress:
progress(0.93, desc="💎 Offloading 1080p HD RIFE to RTX 3060 GPU...")
with urllib.request.urlopen(req, timeout=300) as response:
res_bytes = response.read()
ct = response.headers.get("Content-Type", "")
if "application/json" in ct:
try:
json_data = json.loads(res_bytes.decode("utf-8"))
video_url = json_data.get("video_url") or json_data.get("url") or json_data.get("video")
if video_url:
print(f"✅ VIP RIFE returned direct Video URL: {video_url}")
if progress:
progress(1.0, desc="✅ VIP 1080p HD Acceleration Complete!")
return video_url
except Exception as e:
print(f"Notice parsing JSON video_url: {e}")
if response.status == 200 and res_bytes:
if progress:
progress(0.97, desc="📥 Writing 1080p HD video result...")
out_filename = f"vip_rife_{multiplier}x_{os.path.basename(video_path)}"
out_path = os.path.join(tempfile.gettempdir(), out_filename)
with open(out_path, "wb") as f:
f.write(res_bytes)
print(f"✅ Sulphur AI VIP RIFE Acceleration succeeded: {out_path}")
if progress:
progress(1.0, desc="✅ VIP 1080p HD Acceleration Complete!")
return out_path
except Exception as e:
print(f"⚠️ VIP RIFE API notice: {e}")
return None