areebsatin commited on
Commit
9a3172c
·
0 Parent(s):

Initialize modern architecture for 4K Upscaler Space

Browse files
Files changed (7) hide show
  1. README.md +98 -0
  2. __pycache__/app.cpython-313.pyc +0 -0
  3. app.py +203 -0
  4. core.py +177 -0
  5. packages.txt +1 -0
  6. pyproject.toml +37 -0
  7. requirements.txt +8 -0
README.md ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: 4K Upscaler Pro
3
+ emoji: ⬆️
4
+ colorFrom: purple
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: "4.44.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ ---
12
+
13
+ # ⬆ 4K Upscaler Pro
14
+
15
+ AI-powered image & video upscaler supporting up to 4K resolution (3840×2160).
16
+
17
+ ## Features
18
+ - 🖼 Image upscaling (PNG, JPG, WEBP)
19
+ - 🎬 Video upscaling (MP4, AVI, MOV)
20
+ - 🤖 Real-ESRGAN AI upscaling (GPU mode) + Lanczos fallback (CPU)
21
+ - 📡 Full REST API for n8n, Zapier, or any automation tool
22
+ - 🎯 Targets: 4K, 2K, 1080p, or 2× original
23
+
24
+ ## Deployment Guide
25
+
26
+ ### Step 1: Create Hugging Face Space
27
+ 1. Go to https://huggingface.co/new-space
28
+ 2. Name it: `4k-upscaler`
29
+ 3. SDK: **Gradio**
30
+ 4. Hardware: **CPU Basic** (free) or **T4 GPU** for Real-ESRGAN
31
+
32
+ ### Step 2: Upload Files
33
+ Upload all essential files to your Space:
34
+ - `app.py`
35
+ - `core.py`
36
+ - `requirements.txt`
37
+ - `pyproject.toml`
38
+ - `packages.txt` (adds ffmpeg for video rendering)
39
+ ### Step 3: Get Your API URL
40
+ Your API will be live at:
41
+ ```
42
+ https://<your-username>-4k-upscaler.hf.space/api/predict
43
+ ```
44
+
45
+ ---
46
+
47
+ ## n8n Integration
48
+
49
+ ### Image Upscaling via n8n HTTP Request Node
50
+
51
+ ```
52
+ Method: POST
53
+ URL: https://<your-username>-4k-upscaler.hf.space/api/predict
54
+ Headers:
55
+ Content-Type: application/json
56
+ Authorization: Bearer <YOUR_HF_TOKEN> (only if Space is private)
57
+
58
+ Body (JSON):
59
+ {
60
+ "data": [
61
+ "data:image/png;base64,<BASE64_IMAGE>",
62
+ "Lanczos (Fast)",
63
+ "4K (3840×2160)"
64
+ ]
65
+ }
66
+ ```
67
+
68
+ ### n8n Workflow Steps
69
+ 1. **Trigger** (e.g., HTTP webhook or file watch)
70
+ 2. **Read Binary Data** → convert image to base64
71
+ 3. **HTTP Request** → POST to HF Space API
72
+ 4. **Extract Result** → `{{ $json.data[0] }}` = base64 upscaled image
73
+ 5. **Write File** → save the upscaled image
74
+
75
+ ### Video Upscaling (fn_index: 1)
76
+ ```json
77
+ {
78
+ "fn_index": 1,
79
+ "data": [
80
+ "<video_url_or_base64>",
81
+ "Lanczos (Fast)",
82
+ "4K (3840×2160)"
83
+ ]
84
+ }
85
+ ```
86
+
87
+ ---
88
+
89
+ ## API Reference
90
+
91
+ | Parameter | Values |
92
+ |-----------|--------|
93
+ | Method | `Real-ESRGAN (AI)` or `Lanczos (Fast)` |
94
+ | Target | `4K (3840×2160)`, `2K (2560×1440)`, `1080p (1920×1080)`, `2x Original` |
95
+
96
+ ## Hardware Recommendation
97
+ - **CPU (Free):** Lanczos fast upscaling works perfectly
98
+ - **T4 GPU ($0.60/hr):** Enables Real-ESRGAN AI upscaling for dramatically better quality
__pycache__/app.cpython-313.pyc ADDED
Binary file (6.4 kB). View file
 
app.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ from core import upscale_image, upscale_video
4
+
5
+ # ─────────────────────────────────────────────
6
+ # Gradio UI
7
+ # ─────────────────────────────────────────────
8
+ CSS = """
9
+ @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;700;800&display=swap');
10
+
11
+ :root {
12
+ --bg: #0a0a0f;
13
+ --surface: #111118;
14
+ --border: #1e1e2e;
15
+ --accent: #7c3aed;
16
+ --accent2: #06b6d4;
17
+ --text: #e2e8f0;
18
+ --muted: #64748b;
19
+ }
20
+
21
+ body, .gradio-container {
22
+ background: var(--bg) !important;
23
+ font-family: 'Syne', sans-serif !important;
24
+ color: var(--text) !important;
25
+ }
26
+
27
+ h1 {
28
+ font-size: 2.5rem !important;
29
+ font-weight: 800 !important;
30
+ background: linear-gradient(135deg, #7c3aed, #06b6d4);
31
+ -webkit-background-clip: text;
32
+ -webkit-text-fill-color: transparent;
33
+ text-align: center;
34
+ padding: 1rem 0;
35
+ }
36
+
37
+ .tab-nav button {
38
+ font-family: 'Space Mono', monospace !important;
39
+ background: var(--surface) !important;
40
+ border: 1px solid var(--border) !important;
41
+ color: var(--muted) !important;
42
+ padding: 8px 20px !important;
43
+ }
44
+
45
+ .tab-nav button.selected {
46
+ background: var(--accent) !important;
47
+ color: white !important;
48
+ border-color: var(--accent) !important;
49
+ }
50
+
51
+ .gr-button-primary {
52
+ background: linear-gradient(135deg, #7c3aed, #06b6d4) !important;
53
+ border: none !important;
54
+ font-family: 'Space Mono', monospace !important;
55
+ font-weight: 700 !important;
56
+ letter-spacing: 0.05em !important;
57
+ }
58
+
59
+ footer { display: none !important; }
60
+ """
61
+
62
+ with gr.Blocks(css=CSS, title="4K Upscaler Pro") as demo:
63
+ gr.Markdown("# ⬆ 4K Upscaler Pro")
64
+ gr.Markdown(
65
+ "<p style='text-align:center; color:#64748b; font-family:Space Mono,monospace'>"
66
+ "AI-powered image & video upscaling • Up to 4K resolution • REST API available"
67
+ "</p>"
68
+ )
69
+
70
+ with gr.Tabs():
71
+ # ── IMAGE TAB ──
72
+ with gr.TabItem("🖼 Image Upscaler"):
73
+ with gr.Row():
74
+ with gr.Column():
75
+ img_input = gr.Image(type="pil", label="Upload Image")
76
+ img_method = gr.Radio(
77
+ ["Real-ESRGAN (AI)", "Lanczos (Fast)"],
78
+ value="Lanczos (Fast)",
79
+ label="Upscaling Method",
80
+ )
81
+ img_target = gr.Dropdown(
82
+ [
83
+ "4K (3840×2160)",
84
+ "2K (2560×1440)",
85
+ "1080p (1920×1080)",
86
+ "2x Original",
87
+ ],
88
+ value="4K (3840×2160)",
89
+ label="Target Resolution",
90
+ )
91
+ img_btn = gr.Button("⬆ Upscale Image", variant="primary")
92
+ with gr.Column():
93
+ img_output = gr.Image(label="Upscaled Image")
94
+ img_info = gr.Textbox(label="Info", interactive=False)
95
+ img_download = gr.File(label="⬇ Download PNG")
96
+
97
+ img_btn.click(
98
+ upscale_image,
99
+ inputs=[img_input, img_method, img_target],
100
+ outputs=[img_output, img_info, img_download],
101
+ )
102
+
103
+ # ── VIDEO TAB ──
104
+ with gr.TabItem("🎬 Video Upscaler"):
105
+ with gr.Row():
106
+ with gr.Column():
107
+ vid_input = gr.Video(label="Upload Video")
108
+ vid_method = gr.Radio(
109
+ ["Real-ESRGAN (AI)", "Lanczos (Fast)"],
110
+ value="Lanczos (Fast)",
111
+ label="Upscaling Method",
112
+ )
113
+ vid_target = gr.Dropdown(
114
+ [
115
+ "4K (3840×2160)",
116
+ "2K (2560×1440)",
117
+ "1080p (1920×1080)",
118
+ "2x Original",
119
+ ],
120
+ value="4K (3840×2160)",
121
+ label="Target Resolution",
122
+ )
123
+ vid_btn = gr.Button("⬆ Upscale Video", variant="primary")
124
+ with gr.Column():
125
+ vid_output = gr.Video(label="Upscaled Video")
126
+ vid_info = gr.Textbox(label="Info", interactive=False)
127
+
128
+ vid_btn.click(
129
+ upscale_video,
130
+ inputs=[vid_input, vid_method, vid_target],
131
+ outputs=[vid_output, vid_info],
132
+ )
133
+
134
+ # ── API DOCS TAB ──
135
+ with gr.TabItem("📡 API Docs"):
136
+ gr.Markdown("""
137
+ ## REST API — Use from n8n, curl, or any HTTP client
138
+
139
+ Hugging Face Spaces expose a built-in API automatically via the `/api/predict` endpoint.
140
+
141
+ ---
142
+
143
+ ### 🖼 Image Upscaling API
144
+
145
+ **POST** `https://<your-username>-4k-upscaler.hf.space/api/predict`
146
+
147
+ ```json
148
+ {
149
+ "data": [
150
+ "<base64_image_string>",
151
+ "Lanczos (Fast)",
152
+ "4K (3840×2160)"
153
+ ]
154
+ }
155
+ ```
156
+
157
+ **Response:**
158
+ ```json
159
+ {
160
+ "data": [
161
+ "<base64_upscaled_image>",
162
+ "✅ Upscaled: 1280×720 → 3840×2160",
163
+ "<download_url>"
164
+ ]
165
+ }
166
+ ```
167
+
168
+ ---
169
+
170
+ ### 🎬 Video Upscaling API
171
+
172
+ **POST** `https://<your-username>-4k-upscaler.hf.space/api/predict`
173
+
174
+ Use fn_index: 1 for video endpoint.
175
+
176
+ ```json
177
+ {
178
+ "fn_index": 1,
179
+ "data": [
180
+ "<video_file_url_or_base64>",
181
+ "Lanczos (Fast)",
182
+ "4K (3840×2160)"
183
+ ]
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ### 🔗 n8n Integration
190
+
191
+ Use the **HTTP Request** node in n8n:
192
+ - **Method:** POST
193
+ - **URL:** `https://<your-username>-4k-upscaler.hf.space/api/predict`
194
+ - **Body:** JSON with `data` array (see above)
195
+ - **Headers:** `Content-Type: application/json`
196
+
197
+ For authenticated spaces, add:
198
+ - **Header:** `Authorization: Bearer <your_HF_token>`
199
+ """)
200
+
201
+ # Launch
202
+ if __name__ == "__main__":
203
+ demo.queue(max_size=5).launch()
core.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tempfile
2
+ from enum import Enum
3
+
4
+ import cv2
5
+ import numpy as np
6
+ from PIL import Image
7
+
8
+ try:
9
+ from basicsr.archs.rrdbnet_arch import RRDBNet
10
+ from realesrgan import RealESRGANer
11
+
12
+ REALESRGAN_AVAILABLE = True
13
+ except ImportError:
14
+ REALESRGAN_AVAILABLE = False
15
+
16
+
17
+ class TargetResolution(str, Enum):
18
+ FOUR_K = "4K (3840×2160)"
19
+ TWO_K = "2K (2560×1440)"
20
+ TEN_EIGHTY = "1080p (1920×1080)"
21
+ TWO_X = "2x Original"
22
+
23
+
24
+ def get_dimensions(orig_w: int, orig_h: int, target: TargetResolution) -> tuple[int, int]:
25
+ """Calculate target dimensions based on original size and target resolution."""
26
+ if target == TargetResolution.FOUR_K:
27
+ max_w, max_h = 3840, 2160
28
+ elif target == TargetResolution.TWO_K:
29
+ max_w, max_h = 2560, 1440
30
+ elif target == TargetResolution.TEN_EIGHTY:
31
+ max_w, max_h = 1920, 1080
32
+ else: # TWO_X
33
+ return orig_w * 2, orig_h * 2
34
+
35
+ scale_w = max_w / orig_w
36
+ scale_h = max_h / orig_h
37
+ scale = min(scale_w, scale_h)
38
+
39
+ return int(orig_w * scale), int(orig_h * scale)
40
+
41
+
42
+ def load_upscaler(scale: int = 4) -> "RealESRGANer | None":
43
+ if not REALESRGAN_AVAILABLE:
44
+ return None
45
+ model = RRDBNet(
46
+ num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=scale
47
+ )
48
+ upscaler = RealESRGANer(
49
+ scale=scale,
50
+ model_path=f"https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x{scale}plus.pth",
51
+ model=model,
52
+ tile=512,
53
+ tile_pad=10,
54
+ pre_pad=0,
55
+ half=False, # CPU-safe
56
+ )
57
+ return upscaler
58
+
59
+
60
+ def opencv_upscale(img_np: np.ndarray, target_w: int, target_h: int) -> np.ndarray:
61
+ return cv2.resize(img_np, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4)
62
+
63
+
64
+ def upscale_image(
65
+ image: Image.Image | None, method: str, target_str: str
66
+ ) -> tuple[Image.Image | None, str, str | None]:
67
+
68
+ if image is None:
69
+ return None, "❌ No image provided.", None
70
+
71
+ try:
72
+ target = TargetResolution(target_str)
73
+ except ValueError:
74
+ target = TargetResolution.FOUR_K
75
+
76
+ orig_w, orig_h = image.size
77
+ img_np = np.array(image)
78
+
79
+ target_w, target_h = get_dimensions(orig_w, orig_h, target)
80
+
81
+ if method == "Real-ESRGAN (AI)" and REALESRGAN_AVAILABLE:
82
+ try:
83
+ upscaler = load_upscaler(scale=4)
84
+ if upscaler:
85
+ output, _ = upscaler.enhance(img_np, outscale=target_w / orig_w)
86
+ output = cv2.resize(output, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4)
87
+ else:
88
+ output = opencv_upscale(img_np, target_w, target_h)
89
+ method = "Lanczos (fallback: ESRGAN uninitialized)"
90
+ except Exception as e:
91
+ output = opencv_upscale(img_np, target_w, target_h)
92
+ method = f"Lanczos (fallback: {str(e)[:60]})"
93
+ else:
94
+ output = opencv_upscale(img_np, target_w, target_h)
95
+ method = "Lanczos (Fast)"
96
+
97
+ result_img = Image.fromarray(output)
98
+ info = f"✅ Upscaled: {orig_w}×{orig_h} → {target_w}×{target_h} | Method: {method}"
99
+
100
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
101
+ result_img.save(tmp.name, format="PNG")
102
+ tmp_path = tmp.name
103
+
104
+ return result_img, info, tmp_path
105
+
106
+
107
+ def upscale_video(
108
+ video_path: str | None, method: str, target_str: str, progress_callback=None
109
+ ) -> tuple[str | None, str]:
110
+
111
+ if not video_path:
112
+ return None, "❌ No video provided."
113
+
114
+ cap = cv2.VideoCapture(video_path)
115
+ if not cap.isOpened():
116
+ return None, "❌ Could not open video."
117
+
118
+ try:
119
+ orig_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
120
+ orig_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
121
+ fps = cap.get(cv2.CAP_PROP_FPS)
122
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
123
+
124
+ try:
125
+ target = TargetResolution(target_str)
126
+ except ValueError:
127
+ target = TargetResolution.FOUR_K
128
+
129
+ target_w, target_h = get_dimensions(orig_w, orig_h, target)
130
+
131
+ out_path = tempfile.mktemp(suffix=".mp4")
132
+ fourcc = cv2.VideoWriter_fourcc(*"mp4v")
133
+ writer = cv2.VideoWriter(out_path, fourcc, fps, (target_w, target_h))
134
+
135
+ if not writer.isOpened():
136
+ return None, "❌ Could not create output video writer."
137
+
138
+ try:
139
+ upscaler = None
140
+ if method == "Real-ESRGAN (AI)" and REALESRGAN_AVAILABLE:
141
+ try:
142
+ upscaler = load_upscaler(scale=4)
143
+ except Exception:
144
+ upscaler = None
145
+
146
+ frame_idx = 0
147
+ while True:
148
+ ret, frame = cap.read()
149
+ if not ret:
150
+ break
151
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
152
+
153
+ if upscaler:
154
+ try:
155
+ out_frame, _ = upscaler.enhance(frame_rgb, outscale=target_w / orig_w)
156
+ out_frame = cv2.resize(
157
+ out_frame, (target_w, target_h), interpolation=cv2.INTER_LANCZOS4
158
+ )
159
+ except Exception:
160
+ out_frame = opencv_upscale(frame_rgb, target_w, target_h)
161
+ else:
162
+ out_frame = opencv_upscale(frame_rgb, target_w, target_h)
163
+
164
+ writer.write(cv2.cvtColor(out_frame, cv2.COLOR_RGB2BGR))
165
+ frame_idx += 1
166
+ if total_frames > 0 and progress_callback:
167
+ progress_callback(
168
+ (frame_idx / total_frames), f"Processing frame {frame_idx}/{total_frames}"
169
+ )
170
+
171
+ info = f"✅ Video upscaled: {orig_w}×{orig_h} → {target_w}×{target_h} | {total_frames} frames @ {fps:.1f}fps"
172
+ finally:
173
+ writer.release()
174
+ finally:
175
+ cap.release()
176
+
177
+ return out_path, info
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
pyproject.toml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "video-image-enhancer"
3
+ version = "0.1.0"
4
+ description = "AI-powered image and video upscaling suite using Real-ESRGAN"
5
+ dependencies = [
6
+ "gradio>=4.0.0",
7
+ "opencv-python-headless",
8
+ "Pillow",
9
+ "numpy",
10
+ "torch",
11
+ "torchvision",
12
+ "realesrgan",
13
+ "basicsr",
14
+ ]
15
+ requires-python = ">=3.12"
16
+
17
+ [build-system]
18
+ requires = ["hatchling"]
19
+ build-backend = "hatchling.build"
20
+
21
+ [tool.ruff]
22
+ line-length = 100
23
+ target-version = "py312"
24
+
25
+ [tool.ruff.lint]
26
+ select = [
27
+ "E", # pycodestyle errors
28
+ "W", # pycodestyle warnings
29
+ "F", # pyflakes
30
+ "I", # isort
31
+ "C", # flake8-comprehensions
32
+ "B", # flake8-bugbear
33
+ "UP", # pyupgrade
34
+ ]
35
+ ignore = [
36
+ "E501", # line too long, handled by ruff format
37
+ ]
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.0.0
2
+ opencv-python-headless>=4.8.0.76
3
+ Pillow>=10.0.0
4
+ numpy>=1.26.0,<2.0.0
5
+ torch>=2.0.0
6
+ torchvision>=0.15.0
7
+ realesrgan
8
+ basicsr