upscalex 0.1.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.
upscalex/__init__.py ADDED
@@ -0,0 +1,11 @@
1
+ """
2
+ UpscaleX MCP/CLI
3
+ High-performance 8K/16K image upscaling with Vulkan GPU acceleration and Model Context Protocol.
4
+ """
5
+
6
+ __version__ = "0.1.0"
7
+
8
+ from .engine import RealESRGANEngine
9
+ from .server import mcp
10
+
11
+ __all__ = ["RealESRGANEngine", "mcp", "__version__"]
upscalex/cli.py ADDED
@@ -0,0 +1,296 @@
1
+ """
2
+ Rich interactive CLI interface for UpscaleX image upscaling, model catalog, and MCP management.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ import click
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
15
+ from rich.table import Table
16
+
17
+ from .engine import MODEL_CATALOG, RealESRGANEngine
18
+ from .server import mcp
19
+ from .utils import download_and_extract_engine, get_system_diagnostics
20
+
21
+ console = Console()
22
+
23
+ @click.group()
24
+ @click.version_option(package_name="upscalex")
25
+ def main():
26
+ """UpscaleX MCP/CLI: Production-grade 8K/16K RealESRGAN Image Upscaler & MCP Server."""
27
+ pass
28
+
29
+ @main.command()
30
+ @click.argument("image_path", type=click.Path(exists=True, dir_okay=False))
31
+ @click.option(
32
+ "-t", "--target",
33
+ default="16k",
34
+ help="Target resolution: '16k' (4x), '8k' (2x supersampled), '4k', '4x', '2x', or 'WxH'.",
35
+ show_default=True,
36
+ )
37
+ @click.option(
38
+ "-m", "--model",
39
+ default="realesrgan-x4plus",
40
+ type=click.Choice(list(MODEL_CATALOG.keys()), case_sensitive=False),
41
+ help="Model identifier to apply.",
42
+ show_default=True,
43
+ )
44
+ @click.option("-o", "--output", default=None, help="Output destination file path.")
45
+ @click.option("--tile-size", default=256, type=int, help="GPU tile size in pixels (safe for 4GB VRAM).", show_default=True)
46
+ @click.option("-g", "--gpu-id", default="auto", type=str, help="Vulkan GPU device index ('auto', integer ID, or 'cpu').", show_default=True)
47
+ @click.option("-x", "--tta", is_flag=True, default=False, help="Enable Test-Time Augmentation (higher quality, slower).")
48
+ def upscale(
49
+ image_path: str,
50
+ target: str,
51
+ model: str,
52
+ output: Optional[str],
53
+ tile_size: int,
54
+ gpu_id: str,
55
+ tta: bool,
56
+ ):
57
+ """Upscale an image to 8K, 16K, or custom print resolutions."""
58
+ try:
59
+ engine = RealESRGANEngine(auto_download=True)
60
+ meta = engine.get_image_metadata(image_path)
61
+ except Exception as e:
62
+ console.print(f"[bold red]Initialization Error:[/bold red] {e}")
63
+ sys.exit(1)
64
+
65
+ console.print(
66
+ Panel.fit(
67
+ f"[bold cyan]Input:[/bold cyan] {meta['file_name']}\n"
68
+ f"[bold cyan]Original Resolution:[/bold cyan] {meta['width']} × {meta['height']} ({meta['megapixels_str']})\n"
69
+ f"[bold cyan]Target Mode:[/bold cyan] [bold green]{target.upper()}[/bold green]\n"
70
+ f"[bold cyan]Model:[/bold cyan] {model} | [bold cyan]Hardware:[/bold cyan] {gpu_id} | [bold cyan]Tile Size:[/bold cyan] {tile_size}px",
71
+ title="[bold yellow]UpscaleX CLI[/bold yellow]",
72
+ border_style="cyan",
73
+ )
74
+ )
75
+
76
+ with Progress(
77
+ SpinnerColumn(),
78
+ TextColumn("[progress.description]{task.description}"),
79
+ BarColumn(),
80
+ TextColumn("[bold yellow]{task.percentage:>3.1f}%"),
81
+ TimeElapsedColumn(),
82
+ console=console,
83
+ ) as progress:
84
+ task_id = progress.add_task("[cyan]Processing tiles...", total=100.0)
85
+
86
+ def on_progress(pct: float):
87
+ progress.update(task_id, completed=pct)
88
+
89
+ try:
90
+ result = engine.upscale(
91
+ input_path=image_path,
92
+ output_path=output,
93
+ target=target,
94
+ model=model,
95
+ tile_size=tile_size,
96
+ gpu_id=gpu_id,
97
+ tta=tta,
98
+ progress_callback=on_progress,
99
+ )
100
+ progress.update(task_id, completed=100.0)
101
+ except Exception as e:
102
+ console.print(f"\n[bold red]Upscaling Failed:[/bold red] {e}")
103
+ sys.exit(1)
104
+
105
+ # Render results table
106
+ table = Table(title="Upscale Completed Successfully", border_style="green", header_style="bold green")
107
+ table.add_column("Property", style="bold")
108
+ table.add_column("Value", style="cyan")
109
+
110
+ table.add_row("Output File", result["output_file"])
111
+ table.add_row("Final Resolution", f"{result['output_resolution']} ({result['output_megapixels']})")
112
+ table.add_row("Resolution Gain", f"{result['original_megapixels']} -> {result['output_megapixels']}")
113
+ table.add_row("File Size", f"{result['file_size_str']}")
114
+ table.add_row("Acceleration", f"{result['acceleration']}")
115
+ table.add_row("Duration", f"{result['processing_seconds']} seconds")
116
+ table.add_row("Model Used", str(result["model_used"]))
117
+
118
+ console.print(table)
119
+
120
+ @main.command()
121
+ @click.argument("image_path", type=click.Path(exists=True, dir_okay=False))
122
+ def recommend(image_path: str):
123
+ """Recommend the optimal model, target resolution, and tile size for an image based on system hardware."""
124
+ try:
125
+ engine = RealESRGANEngine(auto_download=False)
126
+ rec = engine.recommend_model(image_path)
127
+ except Exception as e:
128
+ console.print(f"[bold red]Recommendation Error:[/bold red] {e}")
129
+ sys.exit(1)
130
+
131
+ console.print(
132
+ Panel.fit(
133
+ f"[bold cyan]Input Image:[/bold cyan] {rec['file_name']}\n"
134
+ f"[bold cyan]Content Type:[/bold cyan] [bold green]{rec['detected_content_type']}[/bold green]\n"
135
+ f"[bold cyan]Hardware Tier:[/bold cyan] [bold yellow]{rec['hardware_tier']}[/bold yellow]\n"
136
+ f"[bold cyan]Recommended Model:[/bold cyan] [bold white]{rec['recommended_model']}[/bold white]\n"
137
+ f"[bold cyan]Recommended Target:[/bold cyan] [bold green]{rec['recommended_target'].upper()}[/bold green]\n"
138
+ f"[bold cyan]Recommended Tile Size:[/bold cyan] {rec['recommended_tile_size']}px\n\n"
139
+ f"[bold cyan]Model Selection Reasoning:[/bold cyan]\n{rec['model_selection_reasoning']}\n\n"
140
+ f"[bold cyan]Resolution Reasoning:[/bold cyan]\n{rec['target_resolution_reasoning']}\n\n"
141
+ f"[bold cyan]Ready-to-Run Command:[/bold cyan]\n[bold green]{rec['ready_command']}[/bold green]",
142
+ title="[bold yellow]UpscaleX Smart Recommendation Engine[/bold yellow]",
143
+ border_style="yellow",
144
+ )
145
+ )
146
+
147
+ @main.command()
148
+ @click.argument("model_id")
149
+ def download_model(model_id: str):
150
+ """Download an open-weight model from the catalog into local storage."""
151
+ try:
152
+ engine = RealESRGANEngine(auto_download=False)
153
+ except Exception as e:
154
+ console.print(f"[bold red]Initialization Error:[/bold red] {e}")
155
+ sys.exit(1)
156
+
157
+ if model_id not in MODEL_CATALOG:
158
+ valid = ", ".join(MODEL_CATALOG.keys())
159
+ console.print(f"[bold red]Unknown model:[/bold red] {model_id}\n[cyan]Catalog models:[/cyan] {valid}")
160
+ sys.exit(1)
161
+
162
+ meta = MODEL_CATALOG[model_id]
163
+ console.print(f"[cyan]Downloading model:[/cyan] [bold]{meta['name']}[/bold] ({meta['type']})")
164
+
165
+ with Progress(
166
+ SpinnerColumn(),
167
+ TextColumn("[progress.description]{task.description}"),
168
+ BarColumn(),
169
+ TextColumn("[bold yellow]{task.percentage:>3.1f}%"),
170
+ console=console,
171
+ ) as progress:
172
+ task = progress.add_task("Fetching weights...", total=100.0)
173
+
174
+ def cb(downloaded: int, total: int):
175
+ if total > 0:
176
+ pct = (downloaded / total) * 100.0
177
+ progress.update(task, completed=min(pct, 100.0))
178
+
179
+ try:
180
+ res = engine.download_model(model_id, progress_callback=cb)
181
+ progress.update(task, completed=100.0)
182
+ except Exception as e:
183
+ console.print(f"\n[bold red]Download Failed:[/bold red] {e}")
184
+ sys.exit(1)
185
+
186
+ console.print(f"[bold green]{res['message']}[/bold green] ([cyan]{res.get('size_str', 'ready')}[/cyan])")
187
+
188
+ @main.command()
189
+ @click.argument("image_path", type=click.Path(exists=True, dir_okay=False))
190
+ def info(image_path: str):
191
+ """Inspect image resolution, aspect ratio, file size, and print dimensions at 300 DPI."""
192
+ try:
193
+ engine = RealESRGANEngine(auto_download=False)
194
+ meta = engine.get_image_metadata(image_path)
195
+ except Exception as e:
196
+ console.print(f"[bold red]Error:[/bold red] {e}")
197
+ sys.exit(1)
198
+
199
+ table = Table(title=f"Image Metadata: {meta['file_name']}", border_style="cyan")
200
+ table.add_column("Field", style="bold")
201
+ table.add_column("Value", style="yellow")
202
+
203
+ table.add_row("Dimensions", f"{meta['width']} × {meta['height']}")
204
+ table.add_row("Megapixels", str(meta['megapixels_str']))
205
+ table.add_row("Aspect Ratio", f"{meta['aspect_ratio']} (W:H)")
206
+ table.add_row("Color Mode", str(meta['mode']))
207
+ table.add_row("Format", str(meta['format']))
208
+ table.add_row("File Size", str(meta['file_size_str']))
209
+ table.add_row("Print Size (300 DPI)", str(meta['print_size_300dpi_inches']))
210
+ table.add_row("Print Size (Metric)", str(meta['print_size_300dpi_cm']))
211
+
212
+ console.print(table)
213
+
214
+ @main.command()
215
+ def doctor():
216
+ """Inspect cross-platform system environment, CPU threads, and GPU acceleration hardware."""
217
+ try:
218
+ engine = RealESRGANEngine(auto_download=False)
219
+ diag = engine.get_system_diagnostics()
220
+ except Exception:
221
+ diag = get_system_diagnostics()
222
+
223
+ table = Table(title="UpscaleX System & Hardware Diagnostics", border_style="blue", header_style="bold blue")
224
+ table.add_column("Component", style="bold")
225
+ table.add_column("Details", style="cyan")
226
+
227
+ table.add_row("Operating System", f"{diag['os']} ({diag['os_release']})")
228
+ table.add_row("Architecture", diag['architecture'])
229
+ table.add_row("Python Version", diag['python_version'])
230
+ table.add_row("CPU Logical Cores", str(diag['cpu_threads']))
231
+ table.add_row("Vulkan GPU Available", "Yes" if diag['has_vulkan_gpu'] else "No (CPU Multi-Threaded Mode)")
232
+
233
+ if diag['detected_gpus']:
234
+ for g in diag['detected_gpus']:
235
+ marker = " (Discrete / Default)" if g['id'] == diag.get('recommended_gpu_id') else ""
236
+ table.add_row(f"GPU [{g['id']}]", f"{g['name']}{marker}")
237
+ else:
238
+ table.add_row("GPU Devices", "None detected (Headless or CPU-only environment)")
239
+
240
+ table.add_row("Default Mode", diag['acceleration_mode'])
241
+ table.add_row("Recommended Tile Size", f"{diag['recommended_tile_size']}px")
242
+
243
+ console.print(table)
244
+
245
+ @main.command()
246
+ def models():
247
+ """List all supported models with VRAM requirements, target media, and local status."""
248
+ try:
249
+ engine = RealESRGANEngine(auto_download=False)
250
+ model_list = engine.list_models()
251
+ except Exception:
252
+ model_list = []
253
+
254
+ table = Table(title="UpscaleX Model Catalog & VRAM Requirements", border_style="magenta", header_style="bold magenta")
255
+ table.add_column("Model ID", style="bold cyan")
256
+ table.add_column("Scale", style="green")
257
+ table.add_column("Target Media", style="yellow")
258
+ table.add_column("VRAM Requirement", style="white")
259
+ table.add_column("Status", style="bold")
260
+
261
+ for m in model_list:
262
+ status_text = "[green]Installed[/green]" if m["installed"] else "[dim yellow]Available for download[/dim yellow]"
263
+ table.add_row(m["model_id"], f"{m['scale']}×", m["type"], m["vram_tier"], status_text)
264
+
265
+ console.print(table)
266
+
267
+ @main.command()
268
+ def download_engine():
269
+ """Download and set up the official Real-ESRGAN Vulkan engine binary."""
270
+ console.print("[cyan]Checking Real-ESRGAN binary status...[/cyan]")
271
+ with Progress(
272
+ SpinnerColumn(),
273
+ TextColumn("[progress.description]{task.description}"),
274
+ BarColumn(),
275
+ console=console,
276
+ ) as progress:
277
+ task = progress.add_task("Downloading pre-compiled engine...", total=100.0)
278
+
279
+ def cb(downloaded: int, total: int):
280
+ if total > 0:
281
+ pct = (downloaded / total) * 100.0
282
+ progress.update(task, completed=min(pct, 100.0))
283
+
284
+ bin_path = download_and_extract_engine(progress_callback=cb)
285
+ progress.update(task, completed=100.0)
286
+
287
+ console.print(f"[bold green]Engine ready at:[/bold green] {bin_path}")
288
+
289
+ @main.command()
290
+ def serve():
291
+ """Start the Model Context Protocol (MCP) server over standard I/O."""
292
+ console.print("[bold green]Starting UpscaleX MCP Server on stdio...[/bold green]", file=sys.stderr)
293
+ mcp.run()
294
+
295
+ if __name__ == "__main__":
296
+ main()