Skip to content

Commit 577e02b

Browse files
committed
fix(upscale): resolve realesrgan weights
1 parent 4638ba3 commit 577e02b

4 files changed

Lines changed: 118 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ record also lives in the git commit messages.
118118
- Styled-caption overlay encoding now drains FFmpeg stderr in a background
119119
thread while raw frames are piped to stdin, preventing stderr backpressure
120120
from deadlocking long renders.
121+
- The Real-ESRGAN upscale tier now resolves and caches official model weights
122+
before constructing `RealESRGANer`, instead of passing `model_path=None`.
121123
- **Caption burn-in failed on every Windows drive-letter path.** The
122124
`subtitles=`/`ass=` filter value dropped colon escaping, so `C:/...` was
123125
parsed as filename `C`. Added `escape_filter_path()` with the verified

ROADMAP.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,6 @@ history, not here.
2323
Why: six full :root token redefinitions (12, 4462, 5386, 13215, 15466, 17214), two divergent html.theme-light blocks (16701 vs 17628), three different :focus-visible rules, and a triplicated prefers-reduced-motion block — the effective theme is "whatever the last pass overrode" and ~⅓ of 18k lines is dead weight. Consolidate to one token block per theme; then retokenize the ~340 stray hex literals.
2424
Where: extension/com.opencut.panel/client/style.css
2525

26-
- [ ] P2 — Real-ESRGAN "balanced" upscale tier is dead on arrival
27-
Why: RealESRGANer(model_path=None) crashes in __init__ on every released package, and no pretrained weights are ever resolved; model_name is ignored. Resolve the weight file/URL and pass it.
28-
Where: opencut/core/upscale_pro.py:122-126
29-
3026
- [ ] P2 — Concat lists in ~17 feature modules still use unescaped, platform-codec writes
3127
Why: write_concat_list() now exists in helpers.py (UTF-8 + correct quote escaping) and the merge path uses it, but beat_cuts, ai_intro_gen, auto_dub_pipeline, photo_montage, instant_replay, cursor_zoom, event_recap, stream_highlights, video_360, generative_extend, auto_montage, glitch_effects, hook_generator, fit_to_fill, paper_edit, guest_compilation, stringout_reel still hand-roll open(path,"w") lists that break on apostrophes/non-ASCII names.
3228
Where: opencut/core/* (grep: "file '" writes); replace with helpers.write_concat_list

opencut/core/upscale_pro.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,84 @@
1515
import os
1616
import subprocess
1717
import tempfile
18+
import urllib.request
1819
from typing import Callable, Dict, Optional
1920

2021
from opencut.helpers import ensure_package, get_ffmpeg_path, get_video_info, run_ffmpeg
2122

2223
logger = logging.getLogger("opencut")
2324

25+
REALESRGAN_MODELS_DIR = os.path.join(os.path.expanduser("~"), ".opencut", "models")
26+
REALESRGAN_MODEL_SPECS = {
27+
"RealESRGAN_x4plus": {
28+
"filename": "RealESRGAN_x4plus.pth",
29+
"url": "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth",
30+
"scale": 4,
31+
"num_block": 23,
32+
},
33+
"RealESRGAN_x2plus": {
34+
"filename": "RealESRGAN_x2plus.pth",
35+
"url": "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth",
36+
"scale": 2,
37+
"num_block": 23,
38+
},
39+
"RealESRGAN_x4plus_anime_6B": {
40+
"filename": "RealESRGAN_x4plus_anime_6B.pth",
41+
"url": "https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth",
42+
"scale": 4,
43+
"num_block": 6,
44+
},
45+
}
46+
_REALESRGAN_ALIASES = {
47+
"realesrgan_x4plus": "RealESRGAN_x4plus",
48+
"realesrgan-x4plus": "RealESRGAN_x4plus",
49+
"x4plus": "RealESRGAN_x4plus",
50+
"realesrgan_x2plus": "RealESRGAN_x2plus",
51+
"realesrgan-x2plus": "RealESRGAN_x2plus",
52+
"x2plus": "RealESRGAN_x2plus",
53+
"realesrgan_x4plus_anime_6b": "RealESRGAN_x4plus_anime_6B",
54+
"realesrgan-x4plus-anime-6b": "RealESRGAN_x4plus_anime_6B",
55+
"x4plus_anime_6b": "RealESRGAN_x4plus_anime_6B",
56+
"anime": "RealESRGAN_x4plus_anime_6B",
57+
}
58+
59+
60+
def _canonical_realesrgan_model_name(model_name: str) -> str:
61+
if model_name in REALESRGAN_MODEL_SPECS:
62+
return model_name
63+
normalized = str(model_name or "").strip().lower().replace(" ", "_")
64+
return _REALESRGAN_ALIASES.get(normalized, "RealESRGAN_x4plus")
65+
66+
67+
def _resolve_realesrgan_model_path(
68+
model_name: str,
69+
on_progress: Optional[Callable] = None,
70+
) -> str:
71+
canonical_name = _canonical_realesrgan_model_name(model_name)
72+
spec = REALESRGAN_MODEL_SPECS[canonical_name]
73+
os.makedirs(REALESRGAN_MODELS_DIR, exist_ok=True)
74+
model_path = os.path.join(REALESRGAN_MODELS_DIR, spec["filename"])
75+
if os.path.isfile(model_path) and os.path.getsize(model_path) >= 1024:
76+
return model_path
77+
78+
if on_progress:
79+
on_progress(4, f"Downloading {canonical_name} weights...")
80+
81+
tmp_path = f"{model_path}.download"
82+
try:
83+
urllib.request.urlretrieve(spec["url"], tmp_path)
84+
if not os.path.isfile(tmp_path) or os.path.getsize(tmp_path) < 1024:
85+
raise RuntimeError(f"Downloaded Real-ESRGAN weights are empty: {spec['url']}")
86+
os.replace(tmp_path, model_path)
87+
except Exception:
88+
try:
89+
if os.path.exists(tmp_path):
90+
os.unlink(tmp_path)
91+
except OSError:
92+
pass
93+
raise
94+
return model_path
95+
2496
# ---------------------------------------------------------------------------
2597
# Availability
2698
# ---------------------------------------------------------------------------
@@ -119,9 +191,20 @@ def upscale_realesrgan(
119191
if on_progress:
120192
on_progress(5, "Loading Real-ESRGAN model...")
121193

122-
model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
194+
canonical_model_name = _canonical_realesrgan_model_name(model_name)
195+
model_spec = REALESRGAN_MODEL_SPECS[canonical_model_name]
196+
model_path = _resolve_realesrgan_model_path(canonical_model_name, on_progress)
197+
model_scale = int(model_spec["scale"])
198+
model = RRDBNet(
199+
num_in_ch=3,
200+
num_out_ch=3,
201+
num_feat=64,
202+
num_block=int(model_spec["num_block"]),
203+
num_grow_ch=32,
204+
scale=model_scale,
205+
)
123206
upsampler = RealESRGANer(
124-
scale=4, model_path=None, model=model, tile=tile,
207+
scale=model_scale, model_path=model_path, model=model, tile=tile,
125208
half=torch.cuda.is_available(),
126209
)
127210

tests/test_core_modules_batch2.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,37 @@ def test_get_upscale_capabilities_returns_dict(self):
846846
caps = get_upscale_capabilities()
847847
self.assertIsInstance(caps, dict)
848848

849+
def test_realesrgan_model_resolver_downloads_official_weights(self):
850+
from opencut.core import upscale_pro
851+
852+
old_models_dir = upscale_pro.REALESRGAN_MODELS_DIR
853+
with tempfile.TemporaryDirectory() as tmp_dir:
854+
upscale_pro.REALESRGAN_MODELS_DIR = tmp_dir
855+
try:
856+
with patch("opencut.core.upscale_pro.urllib.request.urlretrieve") as mock_retrieve:
857+
def _fake_retrieve(url, target):
858+
Path(target).write_bytes(b"x" * 2048)
859+
return target, None
860+
861+
mock_retrieve.side_effect = _fake_retrieve
862+
path = upscale_pro._resolve_realesrgan_model_path("realesrgan-x4plus")
863+
finally:
864+
upscale_pro.REALESRGAN_MODELS_DIR = old_models_dir
865+
866+
self.assertTrue(path.endswith("RealESRGAN_x4plus.pth"))
867+
mock_retrieve.assert_called_once()
868+
self.assertIn(
869+
"https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth",
870+
mock_retrieve.call_args[0][0],
871+
)
872+
873+
def test_upscale_realesrgan_uses_resolved_model_path(self):
874+
from opencut.core import upscale_pro
875+
876+
source = Path(upscale_pro.__file__).read_text(encoding="utf-8")
877+
self.assertIn("_resolve_realesrgan_model_path(canonical_model_name", source)
878+
self.assertNotIn("model_path=None", source)
879+
849880
@patch("opencut.core.upscale_pro.get_video_info",
850881
return_value={"width": 640, "height": 480})
851882
@patch("opencut.core.upscale_pro.run_ffmpeg")

0 commit comments

Comments
 (0)