Initial commit of video upscaler
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
|||||||
|
# Python
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
uploads/
|
||||||
|
outputs/
|
||||||
|
temp/
|
||||||
|
server.log
|
||||||
|
.uvicorn.pid
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# 🎬 AI Video Upscaler
|
||||||
|
|
||||||
|
A web-based video upscaling application powered by [Real-ESRGAN ncnn-vulkan](https://github.com/xinntao/Real-ESRGAN). Upload your videos through an intuitive dashboard, select an AI model, and produce high-resolution output — all accelerated by your Vulkan-compatible GPU.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
| Category | Details |
|
||||||
|
|---|---|
|
||||||
|
| **AI Upscaling** | 2×/3×/4× upscaling via Real-ESRGAN ncnn-vulkan with GPU acceleration |
|
||||||
|
| **Web Dashboard** | Modern browser UI for uploading, configuring, and monitoring jobs |
|
||||||
|
| **Multiple Models** | Choose from bundled models (realesrgan-x4plus, realesrgan-x4plus-anime, realesr-animevideov3, etc.) |
|
||||||
|
| **Custom Model Upload** | Upload your own `.param` / `.bin` model files through the UI |
|
||||||
|
| **Frame Preview** | Preview a single upscaled frame before committing to a full job |
|
||||||
|
| **Video Trimming** | Trim start/end times before upscaling to process only the segment you need |
|
||||||
|
| **Post-Processing Filters** | Optional sharpening, denoising, and color-correction filters applied after upscaling |
|
||||||
|
| **Audio & Subtitle Preservation** | Original audio tracks and subtitle streams are automatically muxed into the output |
|
||||||
|
| **Job Queue** | Queue multiple jobs and track progress in real time via WebSocket |
|
||||||
|
| **Webhook Notifications** | Configure a webhook URL to receive a callback when a job completes or fails |
|
||||||
|
| **System Diagnostics** | Built-in diagnostics endpoint reports GPU info, Vulkan status, disk space, and dependency versions |
|
||||||
|
| **Output Gallery** | Browse, preview, and download completed videos from the dashboard |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- **OS:** Linux x86_64
|
||||||
|
- **Python:** 3.10+
|
||||||
|
- **FFmpeg / FFprobe:** installed and available on `$PATH`
|
||||||
|
- **GPU:** Vulkan-compatible GPU (NVIDIA, AMD, or Intel) with up-to-date drivers
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone the repository
|
||||||
|
git clone <repo-url> video_upscaler
|
||||||
|
cd video_upscaler
|
||||||
|
|
||||||
|
# 2. Run the setup script (creates venv, installs deps, downloads Real-ESRGAN binary & models)
|
||||||
|
./setup.sh
|
||||||
|
|
||||||
|
# 3. Start the server
|
||||||
|
python start.py
|
||||||
|
```
|
||||||
|
|
||||||
|
The dashboard will be available at **http://localhost:8000** (or your machine's network IP) by default.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🖥️ Server Management
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python start.py # Start on default port 8000 (binds to 0.0.0.0 for LAN access)
|
||||||
|
python start.py --port 9000 # Start on a custom port
|
||||||
|
python start.py --host 127.0.0.1 # Bind to loopback only (no LAN access)
|
||||||
|
python start.py --no-browser # Don't open browser automatically
|
||||||
|
python stop.py # Stop the server
|
||||||
|
python restart.py # Restart the server
|
||||||
|
python restart.py --port 9000 # Restart on a custom port
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 API Endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `POST` | `/api/upload` | Upload a video file for processing |
|
||||||
|
| `POST` | `/api/upscale/start` | Start an upscale job with the given configuration |
|
||||||
|
| `GET` | `/api/upscale/status/{job_id}` | Get the current status and progress of a job |
|
||||||
|
| `POST` | `/api/upscale/cancel/{job_id}` | Cancel a running or queued job |
|
||||||
|
| `POST` | `/api/preview/generate` | Generate an upscaled preview of a single frame |
|
||||||
|
| `GET` | `/api/models` | List all available upscaling models |
|
||||||
|
| `POST` | `/api/models/upload` | Upload a custom model (`.param` + `.bin`) |
|
||||||
|
| `GET` | `/api/outputs` | List completed output files |
|
||||||
|
| `GET` | `/api/download/{filename}` | Download a completed output video |
|
||||||
|
| `GET` | `/api/diagnostics` | System diagnostics (GPU, Vulkan, disk, dependencies) |
|
||||||
|
| `WS` | `/ws/progress/{job_id}` | WebSocket stream of real-time job progress updates |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
video_upscaler/
|
||||||
|
├── app/
|
||||||
|
│ ├── main.py # FastAPI application & API routes
|
||||||
|
│ └── upscaler.py # Video processing pipeline
|
||||||
|
├── static/
|
||||||
|
│ ├── index.html # Web dashboard
|
||||||
|
│ ├── styles.css # UI styles
|
||||||
|
│ └── app.js # Frontend logic
|
||||||
|
├── realesrgan-bin/
|
||||||
|
│ ├── realesrgan-ncnn-vulkan # Upscaling binary (x86_64 Linux)
|
||||||
|
│ └── models/ # AI model files (.param + .bin)
|
||||||
|
├── uploads/ # Uploaded video files (gitignored)
|
||||||
|
├── outputs/ # Upscaled video files (gitignored)
|
||||||
|
├── temp/ # Temporary frame data (gitignored)
|
||||||
|
├── start.py # Start the server
|
||||||
|
├── stop.py # Stop the server
|
||||||
|
├── restart.py # Restart the server
|
||||||
|
├── setup.sh # First-time setup script
|
||||||
|
├── requirements.txt # Python dependencies
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Troubleshooting
|
||||||
|
|
||||||
|
### Vulkan not detected
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: vkCreateInstance failed
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ensure your GPU drivers are up to date.
|
||||||
|
- Verify Vulkan is working: `vulkaninfo | head -20`.
|
||||||
|
- Install the Vulkan loader if missing: `sudo apt install libvulkan1 vulkan-tools` (Debian/Ubuntu).
|
||||||
|
|
||||||
|
### Out of VRAM
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: vkAllocateMemory failed
|
||||||
|
```
|
||||||
|
|
||||||
|
- Lower the **tile size** in the upscale settings (e.g., from `0` to `128` or `64`).
|
||||||
|
- Close other GPU-intensive applications.
|
||||||
|
- Use a model with lower memory requirements (e.g., `realesr-animevideov3`).
|
||||||
|
|
||||||
|
### Server won't start
|
||||||
|
|
||||||
|
- Check if the port is already in use: `ss -tlnp | grep 8000`.
|
||||||
|
- Review the log file: `cat server.log`.
|
||||||
|
- Ensure the virtual environment is activated or was created by `setup.sh`.
|
||||||
|
|
||||||
|
### FFmpeg errors
|
||||||
|
|
||||||
|
- Confirm `ffmpeg` and `ffprobe` are installed: `ffmpeg -version`.
|
||||||
|
- Make sure the input video is not corrupted: `ffprobe input.mp4`.
|
||||||
|
- Check available disk space — upscaling generates large intermediate frame sequences.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
This project uses [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) by [xinntao](https://github.com/xinntao), licensed under the [BSD-3-Clause License](https://github.com/xinntao/Real-ESRGAN/blob/master/LICENSE).
|
||||||
+684
@@ -0,0 +1,684 @@
|
|||||||
|
import os
|
||||||
|
import uuid
|
||||||
|
import queue
|
||||||
|
import threading
|
||||||
|
import asyncio
|
||||||
|
import subprocess
|
||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
from fastapi import FastAPI, UploadFile, File, Form, BackgroundTasks, HTTPException, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.responses import FileResponse, JSONResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
import app.upscaler as upscaler
|
||||||
|
|
||||||
|
app = FastAPI(title="AI Video Upscaler Dashboard")
|
||||||
|
|
||||||
|
# CORS middleware for local development
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# In-memory databases
|
||||||
|
jobs_db: Dict[str, upscaler.UpscaleJob] = {}
|
||||||
|
ws_connections: Dict[str, List[WebSocket]] = {}
|
||||||
|
preview_db: Dict[str, Dict[str, str]] = {} # preview_id -> {orig, upscaled}
|
||||||
|
|
||||||
|
# FIFO queue for upscaling jobs to prevent GPU memory overload
|
||||||
|
job_queue = queue.Queue()
|
||||||
|
queue_lock = threading.Lock()
|
||||||
|
current_running_job_id = None
|
||||||
|
|
||||||
|
main_loop = None
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup_event():
|
||||||
|
global main_loop
|
||||||
|
main_loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
|
global_webhook_url = None
|
||||||
|
|
||||||
|
class WebhookSetupRequest(BaseModel):
|
||||||
|
url: str
|
||||||
|
|
||||||
|
def send_webhook_notification(url: str, payload: dict):
|
||||||
|
def post():
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
url,
|
||||||
|
data=json.dumps(payload).encode('utf-8'),
|
||||||
|
headers={'Content-Type': 'application/json'}
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as response:
|
||||||
|
response.read()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Webhook notification failed to {url}: {e}")
|
||||||
|
threading.Thread(target=post, daemon=True).start()
|
||||||
|
|
||||||
|
# Broadcast updates to websockets and webhooks
|
||||||
|
def broadcast_progress(job_id: str, data: dict):
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
if job:
|
||||||
|
data["is_preview"] = getattr(job, "is_preview", False)
|
||||||
|
if getattr(job, "is_preview", False):
|
||||||
|
data["original_preview_file"] = f"original_{job_id}.{getattr(job, 'transcode_format', 'mp4')}"
|
||||||
|
|
||||||
|
if job_id in ws_connections and main_loop is not None:
|
||||||
|
active_sockets = list(ws_connections[job_id])
|
||||||
|
for websocket in active_sockets:
|
||||||
|
try:
|
||||||
|
# Schedule the coroutine in the main event loop
|
||||||
|
asyncio.run_coroutine_threadsafe(websocket.send_json(data), main_loop)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error sending websocket message: {e}")
|
||||||
|
|
||||||
|
# Process webhooks
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
webhook_url = None
|
||||||
|
if job and getattr(job, "webhook_url", None):
|
||||||
|
webhook_url = job.webhook_url
|
||||||
|
elif global_webhook_url:
|
||||||
|
webhook_url = global_webhook_url
|
||||||
|
|
||||||
|
if webhook_url:
|
||||||
|
payload = {
|
||||||
|
"job_id": job_id,
|
||||||
|
"status": data.get("status"),
|
||||||
|
"progress": data.get("progress"),
|
||||||
|
"current_frame": data.get("current_frame"),
|
||||||
|
"total_frames": data.get("total_frames"),
|
||||||
|
"eta": data.get("eta"),
|
||||||
|
"error": data.get("error"),
|
||||||
|
"output_file": data.get("output_file")
|
||||||
|
}
|
||||||
|
send_webhook_notification(webhook_url, payload)
|
||||||
|
|
||||||
|
# Sequential queue processor thread
|
||||||
|
def queue_worker():
|
||||||
|
global current_running_job_id
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
job_id = job_queue.get()
|
||||||
|
if job_id is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
if not job or job.status == "cancelled":
|
||||||
|
job_queue.task_done()
|
||||||
|
continue
|
||||||
|
|
||||||
|
with queue_lock:
|
||||||
|
current_running_job_id = job_id
|
||||||
|
|
||||||
|
print(f"Starting job {job_id}...")
|
||||||
|
upscaler.run_upscale_pipeline(job, broadcast_progress)
|
||||||
|
|
||||||
|
with queue_lock:
|
||||||
|
current_running_job_id = None
|
||||||
|
|
||||||
|
job_queue.task_done()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in queue worker: {e}")
|
||||||
|
|
||||||
|
# Start the background queue worker thread
|
||||||
|
worker_thread = threading.Thread(target=queue_worker, daemon=True)
|
||||||
|
worker_thread.start()
|
||||||
|
|
||||||
|
|
||||||
|
class StartUpscaleRequest(BaseModel):
|
||||||
|
file_id: str
|
||||||
|
model: str = "realesr-animevideov3"
|
||||||
|
scale: int = 4
|
||||||
|
tile_size: int = 256
|
||||||
|
preserve_audio: bool = True
|
||||||
|
ss: str | None = None
|
||||||
|
t: str | None = None
|
||||||
|
gpu_ids: str | None = None
|
||||||
|
tta: bool = False
|
||||||
|
unsharp: bool = False
|
||||||
|
double_fps: bool = False
|
||||||
|
preserve_subtitles: bool = True
|
||||||
|
start_sec: float | None = None
|
||||||
|
end_sec: float | None = None
|
||||||
|
crf: int = 18
|
||||||
|
preset: str = "medium"
|
||||||
|
denoise: bool = False
|
||||||
|
sharpen: bool = False
|
||||||
|
interpolation: bool = False
|
||||||
|
webhook_url: str | None = None
|
||||||
|
transcode_format: str = "mp4"
|
||||||
|
is_preview: bool = False
|
||||||
|
|
||||||
|
class PreviewRequest(BaseModel):
|
||||||
|
file_id: str
|
||||||
|
timestamp_sec: float = 1.0
|
||||||
|
model: str = "realesr-animevideov3"
|
||||||
|
scale: int = 4
|
||||||
|
tile_size: int = 256
|
||||||
|
gpu_ids: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# Endpoints
|
||||||
|
@app.post("/api/upload")
|
||||||
|
async def upload_video(file: UploadFile = File(...)):
|
||||||
|
"""Upload video to workspace directory and parse metadata"""
|
||||||
|
file_id = str(uuid.uuid4())
|
||||||
|
ext = os.path.splitext(file.filename)[1].lower()
|
||||||
|
if ext not in [".mp4", ".mkv", ".avi", ".mov", ".webm"]:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid video format. Supported: .mp4, .mkv, .avi, .mov, .webm")
|
||||||
|
|
||||||
|
save_path = os.path.join(upscaler.UPLOAD_DIR, f"{file_id}{ext}")
|
||||||
|
|
||||||
|
with open(save_path, "wb") as buffer:
|
||||||
|
content = await file.read()
|
||||||
|
buffer.write(content)
|
||||||
|
|
||||||
|
info = upscaler.get_video_info(save_path)
|
||||||
|
if not info:
|
||||||
|
# cleanup if reading failed
|
||||||
|
os.remove(save_path)
|
||||||
|
raise HTTPException(status_code=400, detail="Could not read video file metadata. File may be corrupted.")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"file_id": file_id,
|
||||||
|
"filename": file.filename,
|
||||||
|
"path": save_path,
|
||||||
|
"metadata": info
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.post("/api/upscale/start")
|
||||||
|
def start_upscale(req: StartUpscaleRequest):
|
||||||
|
"""Queue upscaling task"""
|
||||||
|
# Find uploaded file
|
||||||
|
file_path = None
|
||||||
|
exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"]
|
||||||
|
for ext in exts:
|
||||||
|
test_path = os.path.join(upscaler.UPLOAD_DIR, f"{req.file_id}{ext}")
|
||||||
|
if os.path.exists(test_path):
|
||||||
|
file_path = test_path
|
||||||
|
break
|
||||||
|
|
||||||
|
if not file_path:
|
||||||
|
raise HTTPException(status_code=404, detail="Uploaded file not found.")
|
||||||
|
|
||||||
|
job_id = str(uuid.uuid4())
|
||||||
|
job = upscaler.UpscaleJob(
|
||||||
|
job_id=job_id,
|
||||||
|
video_path=file_path,
|
||||||
|
model=req.model,
|
||||||
|
scale=req.scale,
|
||||||
|
tile_size=req.tile_size,
|
||||||
|
preserve_audio=req.preserve_audio,
|
||||||
|
ss=req.ss,
|
||||||
|
t=req.t,
|
||||||
|
gpu_ids=req.gpu_ids,
|
||||||
|
tta=req.tta,
|
||||||
|
unsharp=req.unsharp,
|
||||||
|
double_fps=req.double_fps,
|
||||||
|
preserve_subtitles=req.preserve_subtitles,
|
||||||
|
start_sec=req.start_sec,
|
||||||
|
end_sec=req.end_sec,
|
||||||
|
crf=req.crf,
|
||||||
|
preset=req.preset,
|
||||||
|
denoise=req.denoise,
|
||||||
|
sharpen=req.sharpen,
|
||||||
|
interpolation=req.interpolation,
|
||||||
|
webhook_url=req.webhook_url,
|
||||||
|
transcode_format=req.transcode_format,
|
||||||
|
is_preview=req.is_preview
|
||||||
|
)
|
||||||
|
|
||||||
|
jobs_db[job_id] = job
|
||||||
|
job_queue.put(job_id)
|
||||||
|
|
||||||
|
# Broadcast initial queued progress
|
||||||
|
broadcast_progress(job_id, {
|
||||||
|
"status": "queued",
|
||||||
|
"progress": 0.0,
|
||||||
|
"current_frame": 0,
|
||||||
|
"total_frames": 0,
|
||||||
|
"eta": "Calculating..."
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"job_id": job_id,
|
||||||
|
"status": "queued"
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/api/upscale/status/{job_id}")
|
||||||
|
def get_status(job_id: str):
|
||||||
|
"""Get status of upscale job"""
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(status_code=404, detail="Job not found.")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"job_id": job.job_id,
|
||||||
|
"status": job.status,
|
||||||
|
"progress": job.progress,
|
||||||
|
"current_frame": job.current_frame,
|
||||||
|
"total_frames": job.total_frames,
|
||||||
|
"eta": job.eta,
|
||||||
|
"error": job.error,
|
||||||
|
"output_file": os.path.basename(job.output_file) if job.output_file else None,
|
||||||
|
"is_preview": getattr(job, "is_preview", False),
|
||||||
|
"original_preview_file": f"original_{job_id}.{getattr(job, 'transcode_format', 'mp4')}" if getattr(job, "is_preview", False) else None
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.post("/api/upscale/cancel/{job_id}")
|
||||||
|
def cancel_job(job_id: str):
|
||||||
|
"""Cancel a running or queued job"""
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(status_code=404, detail="Job not found.")
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
# Broadcast cancellation status
|
||||||
|
broadcast_progress(job_id, {
|
||||||
|
"status": "cancelled",
|
||||||
|
"progress": job.progress,
|
||||||
|
"current_frame": job.current_frame,
|
||||||
|
"total_frames": job.total_frames,
|
||||||
|
"eta": "N/A"
|
||||||
|
})
|
||||||
|
return {"job_id": job_id, "status": "cancelled"}
|
||||||
|
|
||||||
|
@app.post("/api/preview/generate")
|
||||||
|
async def generate_preview(req: PreviewRequest):
|
||||||
|
"""Generate high quality upscaled single frame preview"""
|
||||||
|
file_path = None
|
||||||
|
exts = [".mp4", ".mkv", ".avi", ".mov", ".webm"]
|
||||||
|
for ext in exts:
|
||||||
|
test_path = os.path.join(upscaler.UPLOAD_DIR, f"{req.file_id}{ext}")
|
||||||
|
if os.path.exists(test_path):
|
||||||
|
file_path = test_path
|
||||||
|
break
|
||||||
|
|
||||||
|
if not file_path:
|
||||||
|
raise HTTPException(status_code=404, detail="Video file not found.")
|
||||||
|
|
||||||
|
preview_id = str(uuid.uuid4())
|
||||||
|
preview_temp_dir = os.path.join(upscaler.TEMP_DIR, f"preview_{preview_id}")
|
||||||
|
os.makedirs(preview_temp_dir, exist_ok=True)
|
||||||
|
|
||||||
|
orig_path = os.path.join(preview_temp_dir, "orig.jpg")
|
||||||
|
upscaled_path = os.path.join(preview_temp_dir, "upscaled.jpg")
|
||||||
|
|
||||||
|
# Extract frame
|
||||||
|
extracted = upscaler.extract_single_frame(file_path, req.timestamp_sec, orig_path)
|
||||||
|
if not extracted:
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to extract preview frame from video.")
|
||||||
|
|
||||||
|
# Upscale frame
|
||||||
|
upscaled = upscaler.upscale_image_file(orig_path, upscaled_path, req.model, req.scale, req.tile_size, req.gpu_ids)
|
||||||
|
if not upscaled:
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to upscale preview frame.")
|
||||||
|
|
||||||
|
preview_db[preview_id] = {
|
||||||
|
"orig": orig_path,
|
||||||
|
"upscaled": upscaled_path
|
||||||
|
}
|
||||||
|
|
||||||
|
return {"preview_id": preview_id}
|
||||||
|
|
||||||
|
@app.get("/api/preview/original/{preview_id}")
|
||||||
|
def get_preview_original(preview_id: str):
|
||||||
|
paths = preview_db.get(preview_id)
|
||||||
|
if not paths or not os.path.exists(paths["orig"]):
|
||||||
|
raise HTTPException(status_code=404, detail="Preview frame not found.")
|
||||||
|
return FileResponse(paths["orig"])
|
||||||
|
|
||||||
|
@app.get("/api/preview/upscaled/{preview_id}")
|
||||||
|
def get_preview_upscaled(preview_id: str):
|
||||||
|
paths = preview_db.get(preview_id)
|
||||||
|
if not paths or not os.path.exists(paths["upscaled"]):
|
||||||
|
raise HTTPException(status_code=404, detail="Preview frame not found.")
|
||||||
|
return FileResponse(paths["upscaled"])
|
||||||
|
|
||||||
|
@app.get("/api/models")
|
||||||
|
def get_models():
|
||||||
|
"""Scan models directory and return list of available models"""
|
||||||
|
models_dir = os.path.join(upscaler.BASE_DIR, "realesrgan-bin", "models")
|
||||||
|
if not os.path.exists(models_dir):
|
||||||
|
return []
|
||||||
|
|
||||||
|
models = set()
|
||||||
|
for filename in os.listdir(models_dir):
|
||||||
|
if filename.endswith(".param"):
|
||||||
|
name = filename[:-6] # strip '.param'
|
||||||
|
# Strip scale suffix if present
|
||||||
|
for suffix in ["-x2", "-x3", "-x4"]:
|
||||||
|
if name.endswith(suffix):
|
||||||
|
name = name[:-len(suffix)]
|
||||||
|
break
|
||||||
|
models.add(name)
|
||||||
|
|
||||||
|
return sorted(list(models))
|
||||||
|
|
||||||
|
@app.get("/api/download/{filename}")
|
||||||
|
def download_file(filename: str):
|
||||||
|
file_path = os.path.join(upscaler.OUTPUT_DIR, filename)
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise HTTPException(status_code=404, detail="File not found.")
|
||||||
|
return FileResponse(file_path, media_type="application/octet-stream", filename=filename)
|
||||||
|
|
||||||
|
def safe_delete_file(file_path: str):
|
||||||
|
if file_path and os.path.exists(file_path):
|
||||||
|
try:
|
||||||
|
os.remove(file_path)
|
||||||
|
print(f"Deleted file: {file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to delete {file_path}: {e}")
|
||||||
|
|
||||||
|
@app.get("/api/jobs")
|
||||||
|
def list_jobs():
|
||||||
|
"""List details of all submitted jobs"""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"job_id": job.job_id,
|
||||||
|
"status": job.status,
|
||||||
|
"progress": job.progress,
|
||||||
|
"current_frame": job.current_frame,
|
||||||
|
"total_frames": job.total_frames,
|
||||||
|
"eta": job.eta,
|
||||||
|
"error": job.error,
|
||||||
|
"model": job.model,
|
||||||
|
"scale": job.scale,
|
||||||
|
"output_file": os.path.basename(job.output_file) if job.output_file else None,
|
||||||
|
"video_path": job.video_path,
|
||||||
|
"is_preview": getattr(job, "is_preview", False)
|
||||||
|
}
|
||||||
|
for job in jobs_db.values()
|
||||||
|
]
|
||||||
|
|
||||||
|
@app.delete("/api/jobs/{job_id}")
|
||||||
|
@app.post("/api/jobs/delete/{job_id}")
|
||||||
|
def delete_job(job_id: str):
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
if not job:
|
||||||
|
raise HTTPException(status_code=404, detail="Job not found.")
|
||||||
|
|
||||||
|
# Safely delete uploaded input file
|
||||||
|
if job.video_path:
|
||||||
|
safe_delete_file(job.video_path)
|
||||||
|
|
||||||
|
# Safely delete original preview video if present
|
||||||
|
for ext in [".mp4", ".mkv", ".avi", ".mov", ".webm"]:
|
||||||
|
orig_prev_path = os.path.join(upscaler.OUTPUT_DIR, f"original_{job_id}{ext}")
|
||||||
|
if os.path.exists(orig_prev_path):
|
||||||
|
safe_delete_file(orig_prev_path)
|
||||||
|
|
||||||
|
# Safely delete upscaled output file
|
||||||
|
if job.output_file:
|
||||||
|
safe_delete_file(job.output_file)
|
||||||
|
|
||||||
|
# Clean up any residual temp folder
|
||||||
|
job_temp_dir = os.path.join(upscaler.TEMP_DIR, job_id)
|
||||||
|
if os.path.exists(job_temp_dir):
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(job_temp_dir)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Delete from in-memory db
|
||||||
|
if job_id in jobs_db:
|
||||||
|
del jobs_db[job_id]
|
||||||
|
|
||||||
|
return {"job_id": job_id, "status": "purged"}
|
||||||
|
|
||||||
|
@app.post("/api/jobs/purge-all")
|
||||||
|
def purge_all_jobs():
|
||||||
|
# Purge uploads folder
|
||||||
|
for f in os.listdir(upscaler.UPLOAD_DIR):
|
||||||
|
safe_delete_file(os.path.join(upscaler.UPLOAD_DIR, f))
|
||||||
|
|
||||||
|
# Purge outputs folder
|
||||||
|
for f in os.listdir(upscaler.OUTPUT_DIR):
|
||||||
|
safe_delete_file(os.path.join(upscaler.OUTPUT_DIR, f))
|
||||||
|
|
||||||
|
# Purge temp folder
|
||||||
|
for f in os.listdir(upscaler.TEMP_DIR):
|
||||||
|
path = os.path.join(upscaler.TEMP_DIR, f)
|
||||||
|
try:
|
||||||
|
if os.path.isdir(path):
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(path)
|
||||||
|
else:
|
||||||
|
os.remove(path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Reset in-memory database
|
||||||
|
jobs_db.clear()
|
||||||
|
|
||||||
|
return {"status": "all purged"}
|
||||||
|
|
||||||
|
# Websocket endpoint for real-time progress updates
|
||||||
|
@app.websocket("/ws/progress/{job_id}")
|
||||||
|
async def websocket_progress(websocket: WebSocket, job_id: str):
|
||||||
|
await websocket.accept()
|
||||||
|
if job_id not in ws_connections:
|
||||||
|
ws_connections[job_id] = []
|
||||||
|
ws_connections[job_id].append(websocket)
|
||||||
|
|
||||||
|
# Send current state immediately
|
||||||
|
job = jobs_db.get(job_id)
|
||||||
|
if job:
|
||||||
|
await websocket.send_json({
|
||||||
|
"status": job.status,
|
||||||
|
"progress": job.progress,
|
||||||
|
"current_frame": job.current_frame,
|
||||||
|
"total_frames": job.total_frames,
|
||||||
|
"eta": job.eta,
|
||||||
|
"error": job.error,
|
||||||
|
"output_file": os.path.basename(job.output_file) if job.output_file else None
|
||||||
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# Just keep the connection alive
|
||||||
|
await websocket.receive_text()
|
||||||
|
except WebSocketDisconnect:
|
||||||
|
if job_id in ws_connections:
|
||||||
|
ws_connections[job_id].remove(websocket)
|
||||||
|
if not ws_connections[job_id]:
|
||||||
|
del ws_connections[job_id]
|
||||||
|
|
||||||
|
# Clean up previews on exit
|
||||||
|
@app.on_event("shutdown")
|
||||||
|
def cleanup_previews():
|
||||||
|
for preview_id, paths in preview_db.items():
|
||||||
|
try:
|
||||||
|
parent_dir = os.path.dirname(paths["orig"])
|
||||||
|
if os.path.exists(parent_dir):
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(parent_dir)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# System Diagnostics Endpoint
|
||||||
|
@app.get("/api/diagnostics")
|
||||||
|
def get_diagnostics():
|
||||||
|
# RAM Diagnostics
|
||||||
|
ram = {"total": 0, "available": 0, "used": 0, "percent": 0.0}
|
||||||
|
try:
|
||||||
|
if os.path.exists('/proc/meminfo'):
|
||||||
|
mem_info = {}
|
||||||
|
with open('/proc/meminfo', 'r') as f:
|
||||||
|
for line in f:
|
||||||
|
parts = line.split(':')
|
||||||
|
if len(parts) == 2:
|
||||||
|
name = parts[0].strip()
|
||||||
|
val = parts[1].replace('kB', '').strip()
|
||||||
|
mem_info[name] = int(val)
|
||||||
|
total = mem_info.get('MemTotal', 0) * 1024
|
||||||
|
free = mem_info.get('MemFree', 0) * 1024
|
||||||
|
available = mem_info.get('MemAvailable', total - free) * 1024
|
||||||
|
used = total - available
|
||||||
|
percent = round((used / total) * 100, 1) if total > 0 else 0.0
|
||||||
|
ram = {
|
||||||
|
"total": total,
|
||||||
|
"available": available,
|
||||||
|
"used": used,
|
||||||
|
"percent": percent
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting RAM diagnostics: {e}")
|
||||||
|
|
||||||
|
# CPU Diagnostics
|
||||||
|
cpu = {"count": os.cpu_count(), "load_avg": [], "percent": 0.0}
|
||||||
|
try:
|
||||||
|
load_1, load_5, load_15 = os.getloadavg()
|
||||||
|
cpu["load_avg"] = [load_1, load_5, load_15]
|
||||||
|
cpu["percent"] = min(100.0, round((load_1 / cpu["count"]) * 100, 1)) if cpu["count"] else 0.0
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting CPU diagnostics: {e}")
|
||||||
|
|
||||||
|
# GPU Diagnostics
|
||||||
|
gpu = {"available": False, "gpus": []}
|
||||||
|
try:
|
||||||
|
cmd = [
|
||||||
|
"nvidia-smi",
|
||||||
|
"--query-gpu=utilization.gpu,utilization.memory,memory.total,memory.free,memory.used,name,temperature.gpu",
|
||||||
|
"--format=csv,noheader,nounits"
|
||||||
|
]
|
||||||
|
res = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
lines = res.stdout.strip().split('\n')
|
||||||
|
gpu_list = []
|
||||||
|
for line in lines:
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
parts = [p.strip() for p in line.split(',')]
|
||||||
|
if len(parts) >= 7:
|
||||||
|
gpu_list.append({
|
||||||
|
"name": parts[5],
|
||||||
|
"gpu_utilization_percent": float(parts[0]),
|
||||||
|
"memory_utilization_percent": float(parts[1]),
|
||||||
|
"memory_total_mb": float(parts[2]),
|
||||||
|
"memory_free_mb": float(parts[3]),
|
||||||
|
"memory_used_mb": float(parts[4]),
|
||||||
|
"temperature_c": float(parts[6])
|
||||||
|
})
|
||||||
|
if gpu_list:
|
||||||
|
gpu["available"] = True
|
||||||
|
gpu["gpus"] = gpu_list
|
||||||
|
except Exception as e:
|
||||||
|
gpu["error"] = str(e)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"ram": ram,
|
||||||
|
"cpu": cpu,
|
||||||
|
"gpu": gpu
|
||||||
|
}
|
||||||
|
|
||||||
|
# Outputs Gallery Endpoints
|
||||||
|
@app.get("/api/outputs")
|
||||||
|
def list_outputs():
|
||||||
|
output_dir = upscaler.OUTPUT_DIR
|
||||||
|
if not os.path.exists(output_dir):
|
||||||
|
return []
|
||||||
|
|
||||||
|
files = []
|
||||||
|
for filename in os.listdir(output_dir):
|
||||||
|
if filename.startswith("original_"):
|
||||||
|
continue
|
||||||
|
file_path = os.path.join(output_dir, filename)
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
stat = os.stat(file_path)
|
||||||
|
files.append({
|
||||||
|
"filename": filename,
|
||||||
|
"size": stat.st_size,
|
||||||
|
"modified": stat.st_mtime,
|
||||||
|
"url": f"/api/download/{filename}"
|
||||||
|
})
|
||||||
|
files.sort(key=lambda x: x["modified"], reverse=True)
|
||||||
|
return files
|
||||||
|
|
||||||
|
@app.delete("/api/outputs/delete/{filename}")
|
||||||
|
@app.post("/api/outputs/delete/{filename}")
|
||||||
|
def delete_output(filename: str):
|
||||||
|
file_path = os.path.join(upscaler.OUTPUT_DIR, filename)
|
||||||
|
resolved_path = os.path.abspath(file_path)
|
||||||
|
if not resolved_path.startswith(os.path.abspath(upscaler.OUTPUT_DIR)):
|
||||||
|
raise HTTPException(status_code=403, detail="Access denied.")
|
||||||
|
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise HTTPException(status_code=404, detail="File not found.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.remove(file_path)
|
||||||
|
return {"filename": filename, "status": "deleted"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Failed to delete file: {e}")
|
||||||
|
|
||||||
|
# Custom Model Uploading Endpoint
|
||||||
|
@app.post("/api/models/upload")
|
||||||
|
async def upload_model(
|
||||||
|
param_file: UploadFile = File(None),
|
||||||
|
bin_file: UploadFile = File(None),
|
||||||
|
files: List[UploadFile] = File(None)
|
||||||
|
):
|
||||||
|
models_dir = os.path.join(upscaler.BASE_DIR, "realesrgan-bin", "models")
|
||||||
|
os.makedirs(models_dir, exist_ok=True)
|
||||||
|
|
||||||
|
uploaded_files = []
|
||||||
|
all_files = []
|
||||||
|
if files:
|
||||||
|
all_files.extend(files)
|
||||||
|
if param_file:
|
||||||
|
all_files.append(param_file)
|
||||||
|
if bin_file:
|
||||||
|
all_files.append(bin_file)
|
||||||
|
|
||||||
|
if not all_files:
|
||||||
|
raise HTTPException(status_code=400, detail="No files uploaded. Please upload .param or .bin files.")
|
||||||
|
|
||||||
|
for f in all_files:
|
||||||
|
filename = f.filename
|
||||||
|
ext = os.path.splitext(filename)[1].lower()
|
||||||
|
if ext not in [".param", ".bin"]:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Invalid file extension: {ext}. Only .param and .bin files are allowed for models."
|
||||||
|
)
|
||||||
|
|
||||||
|
safe_filename = os.path.basename(filename)
|
||||||
|
save_path = os.path.join(models_dir, safe_filename)
|
||||||
|
|
||||||
|
with open(save_path, "wb") as buffer:
|
||||||
|
content = await f.read()
|
||||||
|
buffer.write(content)
|
||||||
|
uploaded_files.append(safe_filename)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"uploaded": uploaded_files
|
||||||
|
}
|
||||||
|
|
||||||
|
# Webhook Setup/Retrieval Endpoints
|
||||||
|
@app.post("/api/webhook/setup")
|
||||||
|
def setup_webhook(req: WebhookSetupRequest):
|
||||||
|
global global_webhook_url
|
||||||
|
global_webhook_url = req.url
|
||||||
|
return {"status": "success", "webhook_url": global_webhook_url}
|
||||||
|
|
||||||
|
@app.get("/api/uploads/{filename}")
|
||||||
|
def get_upload_file(filename: str):
|
||||||
|
file_path = os.path.join(upscaler.UPLOAD_DIR, filename)
|
||||||
|
resolved_path = os.path.abspath(file_path)
|
||||||
|
if not resolved_path.startswith(os.path.abspath(upscaler.UPLOAD_DIR)):
|
||||||
|
raise HTTPException(status_code=403, detail="Access denied.")
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
raise HTTPException(status_code=404, detail="File not found.")
|
||||||
|
return FileResponse(file_path)
|
||||||
|
|
||||||
|
# Mount static folder
|
||||||
|
app.mount("/", StaticFiles(directory=os.path.join(upscaler.BASE_DIR, "static"), html=True), name="static")
|
||||||
+519
@@ -0,0 +1,519 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from typing import Dict, Any, Callable
|
||||||
|
|
||||||
|
# Paths config
|
||||||
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
BIN_PATH = os.path.join(BASE_DIR, "realesrgan-bin", "realesrgan-ncnn-vulkan")
|
||||||
|
UPLOAD_DIR = os.path.join(BASE_DIR, "uploads")
|
||||||
|
OUTPUT_DIR = os.path.join(BASE_DIR, "outputs")
|
||||||
|
TEMP_DIR = os.path.join(BASE_DIR, "temp")
|
||||||
|
|
||||||
|
# Ensure directories exist
|
||||||
|
for d in [UPLOAD_DIR, OUTPUT_DIR, TEMP_DIR]:
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
|
||||||
|
class UpscaleJob:
|
||||||
|
def __init__(self, job_id: str, video_path: str, model: str, scale: int, tile_size: int, preserve_audio: bool,
|
||||||
|
ss: str = None, t: str = None, gpu_ids: str = None, tta: bool = False,
|
||||||
|
unsharp: bool = False, double_fps: bool = False, preserve_subtitles: bool = True,
|
||||||
|
start_sec: float = None, end_sec: float = None, crf: int = 18, preset: str = "medium",
|
||||||
|
denoise: bool = False, sharpen: bool = False, interpolation: bool = False,
|
||||||
|
webhook_url: str = None, transcode_format: str = "mp4", is_preview: bool = False):
|
||||||
|
self.job_id = job_id
|
||||||
|
self.video_path = video_path
|
||||||
|
self.model = model
|
||||||
|
self.scale = scale
|
||||||
|
self.tile_size = tile_size
|
||||||
|
self.preserve_audio = preserve_audio
|
||||||
|
|
||||||
|
# Trim mapping
|
||||||
|
if ss is not None:
|
||||||
|
self.ss = ss
|
||||||
|
elif start_sec is not None:
|
||||||
|
self.ss = str(start_sec)
|
||||||
|
else:
|
||||||
|
self.ss = None
|
||||||
|
|
||||||
|
if t is not None:
|
||||||
|
self.t = t
|
||||||
|
elif end_sec is not None:
|
||||||
|
start = float(self.ss) if self.ss is not None else 0.0
|
||||||
|
self.t = str(max(0.0, end_sec - start))
|
||||||
|
else:
|
||||||
|
self.t = None
|
||||||
|
|
||||||
|
self.gpu_ids = gpu_ids
|
||||||
|
self.tta = tta
|
||||||
|
self.unsharp = unsharp
|
||||||
|
self.double_fps = double_fps
|
||||||
|
self.preserve_subtitles = preserve_subtitles
|
||||||
|
self.start_sec = start_sec
|
||||||
|
self.end_sec = end_sec
|
||||||
|
self.crf = crf
|
||||||
|
self.preset = preset
|
||||||
|
self.denoise = denoise
|
||||||
|
self.sharpen = sharpen
|
||||||
|
self.interpolation = interpolation
|
||||||
|
self.webhook_url = webhook_url
|
||||||
|
self.transcode_format = transcode_format
|
||||||
|
self.is_preview = is_preview
|
||||||
|
self.original_preview_file = None
|
||||||
|
|
||||||
|
self.status = "pending"
|
||||||
|
self.progress = 0.0
|
||||||
|
self.current_frame = 0
|
||||||
|
self.total_frames = 0
|
||||||
|
self.eta = "Calculating..."
|
||||||
|
self.error = None
|
||||||
|
self.start_time = None
|
||||||
|
self.output_file = None
|
||||||
|
|
||||||
|
# Track processes to allow cancellation
|
||||||
|
self._processes = []
|
||||||
|
self._is_cancelled = False
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def update_status(self, status: str, progress: float = None, current_frame: int = None, eta: str = None, error: str = None):
|
||||||
|
with self._lock:
|
||||||
|
self.status = status
|
||||||
|
if progress is not None:
|
||||||
|
self.progress = round(progress, 2)
|
||||||
|
if current_frame is not None:
|
||||||
|
self.current_frame = current_frame
|
||||||
|
if eta is not None:
|
||||||
|
self.eta = eta
|
||||||
|
if error is not None:
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
with self._lock:
|
||||||
|
self._is_cancelled = True
|
||||||
|
self.status = "cancelled"
|
||||||
|
self.eta = "N/A"
|
||||||
|
for p in self._processes:
|
||||||
|
try:
|
||||||
|
p.terminate()
|
||||||
|
p.wait(timeout=2)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
p.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
self._processes.clear()
|
||||||
|
|
||||||
|
def run_command(self, cmd: list, shell=False) -> subprocess.Popen:
|
||||||
|
with self._lock:
|
||||||
|
if self._is_cancelled:
|
||||||
|
raise InterruptedError("Job was cancelled")
|
||||||
|
|
||||||
|
p = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
shell=shell
|
||||||
|
)
|
||||||
|
self._processes.append(p)
|
||||||
|
return p
|
||||||
|
|
||||||
|
def cleanup_process(self, p: subprocess.Popen):
|
||||||
|
with self._lock:
|
||||||
|
if p in self._processes:
|
||||||
|
self._processes.remove(p)
|
||||||
|
|
||||||
|
def get_video_info(video_path: str) -> dict:
|
||||||
|
"""Extract metadata using ffprobe"""
|
||||||
|
cmd = [
|
||||||
|
"ffprobe",
|
||||||
|
"-v", "error",
|
||||||
|
"-select_streams", "v:0",
|
||||||
|
"-show_entries", "stream=avg_frame_rate,duration,width,height,codec_name,nb_frames",
|
||||||
|
"-of", "json",
|
||||||
|
video_path
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
|
data = json.loads(result.stdout)
|
||||||
|
if not data.get("streams"):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
stream = data["streams"][0]
|
||||||
|
|
||||||
|
# Calculate FPS
|
||||||
|
fps_str = stream.get("avg_frame_rate", "30/1")
|
||||||
|
if "/" in fps_str:
|
||||||
|
num, den = map(float, fps_str.split("/"))
|
||||||
|
fps = num / den if den != 0 else 30.0
|
||||||
|
else:
|
||||||
|
fps = float(fps_str) if fps_str else 30.0
|
||||||
|
|
||||||
|
# Get total frames
|
||||||
|
nb_frames = stream.get("nb_frames")
|
||||||
|
if nb_frames and nb_frames.isdigit():
|
||||||
|
total_frames = int(nb_frames)
|
||||||
|
else:
|
||||||
|
duration = float(stream.get("duration", 0))
|
||||||
|
total_frames = int(duration * fps)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"width": int(stream.get("width", 0)),
|
||||||
|
"height": int(stream.get("height", 0)),
|
||||||
|
"fps": round(fps, 3),
|
||||||
|
"duration": round(float(stream.get("duration", 0)), 2),
|
||||||
|
"codec": stream.get("codec_name", "unknown"),
|
||||||
|
"total_frames": total_frames
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading video info: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def extract_single_frame(video_path: str, timestamp_sec: float, output_path: str) -> bool:
|
||||||
|
"""Extract a single frame at timestamp for preview"""
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg", "-y",
|
||||||
|
"-ss", str(timestamp_sec),
|
||||||
|
"-i", video_path,
|
||||||
|
"-vframes", "1",
|
||||||
|
"-f", "image2",
|
||||||
|
output_path
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, capture_output=True, check=True)
|
||||||
|
return os.path.exists(output_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error extracting single frame: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def upscale_image_file(input_path: str, output_path: str, model: str, scale: int, tile_size: int, gpu_ids: str = None) -> bool:
|
||||||
|
"""Run Real-ESRGAN on a single image file"""
|
||||||
|
cmd = [
|
||||||
|
BIN_PATH,
|
||||||
|
"-i", input_path,
|
||||||
|
"-o", output_path,
|
||||||
|
"-n", model,
|
||||||
|
"-s", str(scale),
|
||||||
|
"-t", str(tile_size)
|
||||||
|
]
|
||||||
|
if gpu_ids is not None:
|
||||||
|
cmd.extend(["-g", str(gpu_ids)])
|
||||||
|
try:
|
||||||
|
subprocess.run(cmd, capture_output=True, check=True)
|
||||||
|
return os.path.exists(output_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error upscaling single image: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def run_upscale_pipeline(job: UpscaleJob, on_progress_update: Callable[[str, Dict[str, Any]], None]):
|
||||||
|
job.start_time = time.time()
|
||||||
|
job.update_status("analyzing", progress=5)
|
||||||
|
|
||||||
|
# Get video info
|
||||||
|
info = get_video_info(job.video_path)
|
||||||
|
if not info:
|
||||||
|
job.update_status("failed", error="Could not read video metadata.")
|
||||||
|
on_progress_update(job.job_id, {"status": "failed", "error": "Could not read video metadata"})
|
||||||
|
return
|
||||||
|
|
||||||
|
job.total_frames = info["total_frames"]
|
||||||
|
fps = info["fps"]
|
||||||
|
|
||||||
|
# Create job temp directories
|
||||||
|
job_temp_dir = os.path.join(TEMP_DIR, job.job_id)
|
||||||
|
input_frames_dir = os.path.join(job_temp_dir, "input_frames")
|
||||||
|
output_frames_dir = os.path.join(job_temp_dir, "output_frames")
|
||||||
|
|
||||||
|
os.makedirs(input_frames_dir, exist_ok=True)
|
||||||
|
os.makedirs(output_frames_dir, exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# If preview, extract the original 5s clip first
|
||||||
|
transcode_fmt = getattr(job, "transcode_format", "mp4")
|
||||||
|
if getattr(job, "is_preview", False):
|
||||||
|
orig_preview_filename = f"original_{job.job_id}.{transcode_fmt}"
|
||||||
|
orig_preview_filepath = os.path.join(OUTPUT_DIR, orig_preview_filename)
|
||||||
|
job.original_preview_file = orig_preview_filepath
|
||||||
|
|
||||||
|
cut_cmd = ["ffmpeg", "-y"]
|
||||||
|
if job.ss is not None:
|
||||||
|
cut_cmd.extend(["-ss", str(job.ss)])
|
||||||
|
if job.t is not None:
|
||||||
|
cut_cmd.extend(["-t", str(job.t)])
|
||||||
|
cut_cmd.extend([
|
||||||
|
"-i", job.video_path,
|
||||||
|
"-map", "0:v:0",
|
||||||
|
"-map", "0:a:0?",
|
||||||
|
"-c:v", "libx264",
|
||||||
|
"-c:a", "aac",
|
||||||
|
orig_preview_filepath
|
||||||
|
])
|
||||||
|
try:
|
||||||
|
subprocess.run(cut_cmd, capture_output=True, check=True)
|
||||||
|
except Exception as cut_err:
|
||||||
|
print(f"Error cutting original preview video: {cut_err}")
|
||||||
|
|
||||||
|
# Step 1: Extract Frames
|
||||||
|
job.update_status("extracting", progress=10)
|
||||||
|
on_progress_update(job.job_id, {"status": "extracting", "progress": 10})
|
||||||
|
|
||||||
|
# High quality JPG frames to balance disk usage and speed
|
||||||
|
extract_cmd = ["ffmpeg", "-y"]
|
||||||
|
if job.ss is not None:
|
||||||
|
extract_cmd.extend(["-ss", str(job.ss)])
|
||||||
|
if job.t is not None:
|
||||||
|
extract_cmd.extend(["-t", str(job.t)])
|
||||||
|
extract_cmd.extend(["-i", job.video_path])
|
||||||
|
|
||||||
|
# Apply unsharp pre-filter if enabled
|
||||||
|
if getattr(job, "unsharp", False):
|
||||||
|
extract_cmd.extend(["-vf", "unsharp"])
|
||||||
|
|
||||||
|
extract_cmd.extend([
|
||||||
|
"-q:v", "2",
|
||||||
|
os.path.join(input_frames_dir, "frame_%08d.jpg")
|
||||||
|
])
|
||||||
|
|
||||||
|
p_extract = job.run_command(extract_cmd)
|
||||||
|
stdout, stderr = p_extract.communicate()
|
||||||
|
job.cleanup_process(p_extract)
|
||||||
|
|
||||||
|
if p_extract.returncode != 0:
|
||||||
|
raise RuntimeError(f"FFmpeg frame extraction failed: {stderr}")
|
||||||
|
|
||||||
|
# Count actual frames extracted
|
||||||
|
extracted_files = sorted([f for f in os.listdir(input_frames_dir) if f.startswith("frame_")])
|
||||||
|
actual_total = len(extracted_files)
|
||||||
|
if actual_total == 0:
|
||||||
|
raise RuntimeError("No frames extracted from video")
|
||||||
|
|
||||||
|
job.total_frames = actual_total
|
||||||
|
|
||||||
|
# Step 2: Upscale Frames
|
||||||
|
job.update_status("upscaling", progress=20, current_frame=0)
|
||||||
|
on_progress_update(job.job_id, {"status": "upscaling", "progress": 20, "current_frame": 0, "total_frames": actual_total})
|
||||||
|
|
||||||
|
current_tile_size = job.tile_size
|
||||||
|
while True:
|
||||||
|
# Launch Real-ESRGAN on directory
|
||||||
|
upscale_cmd = [
|
||||||
|
BIN_PATH,
|
||||||
|
"-i", input_frames_dir,
|
||||||
|
"-o", output_frames_dir,
|
||||||
|
"-n", job.model,
|
||||||
|
"-s", str(job.scale),
|
||||||
|
"-t", str(current_tile_size),
|
||||||
|
"-f", "jpg"
|
||||||
|
]
|
||||||
|
if getattr(job, "gpu_ids", None) is not None:
|
||||||
|
upscale_cmd.extend(["-g", str(job.gpu_ids)])
|
||||||
|
if getattr(job, "tta", False):
|
||||||
|
upscale_cmd.append("-x")
|
||||||
|
|
||||||
|
upscale_start_time = time.time()
|
||||||
|
p_upscale = job.run_command(upscale_cmd)
|
||||||
|
|
||||||
|
# Monitor thread for output files
|
||||||
|
while p_upscale.poll() is None:
|
||||||
|
if job._is_cancelled:
|
||||||
|
return
|
||||||
|
|
||||||
|
processed_files = len(os.listdir(output_frames_dir))
|
||||||
|
progress_pct = 20.0 + (float(processed_files) / actual_total) * 60.0 # upscaling is 20% to 80%
|
||||||
|
|
||||||
|
# Estimate ETA
|
||||||
|
elapsed = time.time() - upscale_start_time
|
||||||
|
if processed_files > 0:
|
||||||
|
sec_per_frame = elapsed / processed_files
|
||||||
|
rem_frames = actual_total - processed_files
|
||||||
|
eta_sec = rem_frames * sec_per_frame
|
||||||
|
|
||||||
|
# Format ETA
|
||||||
|
if eta_sec > 60:
|
||||||
|
eta_str = f"{int(eta_sec // 60)}m {int(eta_sec % 60)}s"
|
||||||
|
else:
|
||||||
|
eta_str = f"{int(eta_sec)}s"
|
||||||
|
else:
|
||||||
|
eta_str = "Calculating..."
|
||||||
|
|
||||||
|
job.update_status("upscaling", progress=progress_pct, current_frame=processed_files, eta=eta_str)
|
||||||
|
on_progress_update(job.job_id, {
|
||||||
|
"status": "upscaling",
|
||||||
|
"progress": progress_pct,
|
||||||
|
"current_frame": processed_files,
|
||||||
|
"total_frames": actual_total,
|
||||||
|
"eta": eta_str
|
||||||
|
})
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
stdout, stderr = p_upscale.communicate()
|
||||||
|
job.cleanup_process(p_upscale)
|
||||||
|
|
||||||
|
if job._is_cancelled:
|
||||||
|
return
|
||||||
|
|
||||||
|
if p_upscale.returncode != 0:
|
||||||
|
err_msg = (stdout or "") + "\n" + (stderr or "")
|
||||||
|
is_alloc_error = any(x in err_msg.lower() for x in ["vkallocatememory", "out of memory", "allocation", "vram", "failed to allocate"])
|
||||||
|
|
||||||
|
if is_alloc_error:
|
||||||
|
if current_tile_size <= 0:
|
||||||
|
next_tile_size = 256
|
||||||
|
else:
|
||||||
|
next_tile_size = current_tile_size // 2
|
||||||
|
|
||||||
|
if next_tile_size >= 32:
|
||||||
|
print(f"Job {job.job_id}: Real-ESRGAN failed with VRAM allocation error. Retrying with tile size halved from {current_tile_size} to {next_tile_size}.")
|
||||||
|
current_tile_size = next_tile_size
|
||||||
|
# Clean up output frames directory before retrying
|
||||||
|
for filename in os.listdir(output_frames_dir):
|
||||||
|
file_path = os.path.join(output_frames_dir, filename)
|
||||||
|
try:
|
||||||
|
if os.path.isfile(file_path) or os.path.islink(file_path):
|
||||||
|
os.unlink(file_path)
|
||||||
|
elif os.path.isdir(file_path):
|
||||||
|
shutil.rmtree(file_path)
|
||||||
|
except Exception as cleanup_err:
|
||||||
|
print(f"Error cleaning file {file_path}: {cleanup_err}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
raise RuntimeError(f"Real-ESRGAN failed with exit code {p_upscale.returncode}: {err_msg}")
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Final validation of upscale output
|
||||||
|
processed_files = len(os.listdir(output_frames_dir))
|
||||||
|
job.update_status("upscaling", progress=80.0, current_frame=processed_files)
|
||||||
|
|
||||||
|
# Step 3: Reassemble video
|
||||||
|
job.update_status("assembling", progress=85.0)
|
||||||
|
on_progress_update(job.job_id, {"status": "assembling", "progress": 85.0})
|
||||||
|
|
||||||
|
transcode_fmt = getattr(job, "transcode_format", "mp4")
|
||||||
|
out_filename = f"upscaled_{job.job_id}.{transcode_fmt}"
|
||||||
|
out_filepath = os.path.join(OUTPUT_DIR, out_filename)
|
||||||
|
job.output_file = out_filepath
|
||||||
|
|
||||||
|
# Choose codecs based on format
|
||||||
|
vcodec = "libx264"
|
||||||
|
acodec = "copy"
|
||||||
|
if transcode_fmt == "webm":
|
||||||
|
vcodec = "libvpx-vp9"
|
||||||
|
acodec = "libvorbis"
|
||||||
|
|
||||||
|
# Construct ffmpeg reassembly command
|
||||||
|
assemble_cmd = [
|
||||||
|
"ffmpeg", "-y",
|
||||||
|
"-framerate", str(fps),
|
||||||
|
"-i", os.path.join(output_frames_dir, "frame_%08d.jpg")
|
||||||
|
]
|
||||||
|
|
||||||
|
# We need the original video as the second input (index 1) if we preserve audio or subtitles
|
||||||
|
need_orig_input = job.preserve_audio or getattr(job, "preserve_subtitles", True)
|
||||||
|
if need_orig_input:
|
||||||
|
orig_input_cmd = []
|
||||||
|
if getattr(job, "ss", None) is not None:
|
||||||
|
orig_input_cmd.extend(["-ss", str(job.ss)])
|
||||||
|
if getattr(job, "t", None) is not None:
|
||||||
|
orig_input_cmd.extend(["-t", str(job.t)])
|
||||||
|
orig_input_cmd.extend(["-i", job.video_path])
|
||||||
|
assemble_cmd.extend(orig_input_cmd)
|
||||||
|
|
||||||
|
assemble_cmd.extend(["-map", "0:v:0"])
|
||||||
|
|
||||||
|
if job.preserve_audio:
|
||||||
|
assemble_cmd.extend([
|
||||||
|
"-map", "1:a:0?",
|
||||||
|
"-c:a", acodec
|
||||||
|
])
|
||||||
|
|
||||||
|
if getattr(job, "preserve_subtitles", True):
|
||||||
|
assemble_cmd.extend([
|
||||||
|
"-map", "1:s?",
|
||||||
|
"-c:s", "mov_text"
|
||||||
|
])
|
||||||
|
|
||||||
|
# Apply filters
|
||||||
|
vf_filters = []
|
||||||
|
if getattr(job, "denoise", False):
|
||||||
|
vf_filters.append("hqdn3d")
|
||||||
|
if getattr(job, "sharpen", False) or getattr(job, "unsharp", False):
|
||||||
|
vf_filters.append("unsharp=3:3:0.5:3:3:0.5")
|
||||||
|
if getattr(job, "interpolation", False) or getattr(job, "double_fps", False):
|
||||||
|
target_fps = fps * 2 if getattr(job, "double_fps", False) else 60
|
||||||
|
if target_fps < fps:
|
||||||
|
target_fps = fps
|
||||||
|
vf_filters.append(f"minterpolate=fps={target_fps}")
|
||||||
|
|
||||||
|
if vf_filters:
|
||||||
|
assemble_cmd.extend(["-vf", ",".join(vf_filters)])
|
||||||
|
|
||||||
|
assemble_cmd.extend([
|
||||||
|
"-c:v", vcodec,
|
||||||
|
"-pix_fmt", "yuv420p",
|
||||||
|
"-crf", str(getattr(job, "crf", 18)),
|
||||||
|
"-preset", getattr(job, "preset", "medium"),
|
||||||
|
out_filepath
|
||||||
|
])
|
||||||
|
|
||||||
|
p_assemble = job.run_command(assemble_cmd)
|
||||||
|
stdout, stderr = p_assemble.communicate()
|
||||||
|
job.cleanup_process(p_assemble)
|
||||||
|
|
||||||
|
if p_assemble.returncode != 0:
|
||||||
|
raise RuntimeError(f"FFmpeg video assembly failed: {stderr}")
|
||||||
|
|
||||||
|
# Step 4: Complete
|
||||||
|
job.update_status("completed", progress=100.0, eta="Done")
|
||||||
|
on_progress_update(job.job_id, {
|
||||||
|
"status": "completed",
|
||||||
|
"progress": 100.0,
|
||||||
|
"eta": "Done",
|
||||||
|
"output_file": out_filename
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
if not job._is_cancelled:
|
||||||
|
job.update_status("failed", error=str(e))
|
||||||
|
on_progress_update(job.job_id, {"status": "failed", "error": str(e)})
|
||||||
|
finally:
|
||||||
|
# Clean up temp frames to save space
|
||||||
|
try:
|
||||||
|
if os.path.exists(job_temp_dir):
|
||||||
|
shutil.rmtree(job_temp_dir)
|
||||||
|
except Exception as cleanup_err:
|
||||||
|
print(f"Error during temp cleanup: {cleanup_err}")
|
||||||
|
|
||||||
|
# Trigger webhook if URL is provided
|
||||||
|
if getattr(job, "webhook_url", None):
|
||||||
|
def trigger_webhook_task():
|
||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
|
try:
|
||||||
|
payload = {
|
||||||
|
"job_id": job.job_id,
|
||||||
|
"status": job.status,
|
||||||
|
"progress": job.progress,
|
||||||
|
"error": job.error,
|
||||||
|
"output_file": os.path.basename(job.output_file) if job.output_file else None,
|
||||||
|
"model": job.model,
|
||||||
|
"scale": job.scale,
|
||||||
|
"duration": round(time.time() - job.start_time, 2) if job.start_time else 0
|
||||||
|
}
|
||||||
|
req = urllib.request.Request(
|
||||||
|
job.webhook_url,
|
||||||
|
data=json.dumps(payload).encode('utf-8'),
|
||||||
|
headers={'Content-Type': 'application/json'},
|
||||||
|
method='POST'
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as response:
|
||||||
|
pass
|
||||||
|
except Exception as ex:
|
||||||
|
print(f"Error triggering webhook: {ex}")
|
||||||
|
|
||||||
|
threading.Thread(target=trigger_webhook_task, daemon=True).start()
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# RealESRGAN
|
||||||
|
|
||||||
|
We have provided the following models:
|
||||||
|
|
||||||
|
1. realesr-animevideov3 (default)
|
||||||
|
2. realesrgan-x4plus
|
||||||
|
3. realesrgan-x4plus-anime
|
||||||
|
|
||||||
|
Command:
|
||||||
|
|
||||||
|
You may need to change file mode before running.
|
||||||
|
|
||||||
|
chmod u+x realesrgan-ncnn-vulkan
|
||||||
|
|
||||||
|
1. ./realesrgan-ncnn-vulkan -i input.jpg -o output.png
|
||||||
|
2. ./realesrgan-ncnn-vulkan -i input.jpg -o output.png -n realesr-animevideov3
|
||||||
|
3. ./realesrgan-ncnn-vulkan -i input_folder -o outputfolder -n realesr-animevideov3 -s 2 -f jpg
|
||||||
|
4. ./realesrgan-ncnn-vulkan -i input_folder -o outputfolder -n realesr-animevideov3 -s 4 -f jpg
|
||||||
|
|
||||||
|
Commands for enhancing anime videos:
|
||||||
|
|
||||||
|
1. Use ffmpeg to extract frames from a video (Remember to create the folder `tmp_frames` ahead)
|
||||||
|
|
||||||
|
ffmpeg -i onepiece_demo.mp4 -qscale:v 1 -qmin 1 -qmax 1 -vsync 0 tmp_frames/frame%08d.jpg
|
||||||
|
|
||||||
|
2. Inference with Real-ESRGAN executable file (Remember to create the folder `out_frames` ahead)
|
||||||
|
|
||||||
|
./realesrgan-ncnn-vulkan -i tmp_frames -o out_frames -n realesr-animevideov3 -s 2 -f jpg
|
||||||
|
|
||||||
|
3. Merge the enhanced frames back into a video
|
||||||
|
|
||||||
|
ffmpeg -i out_frames/frame%08d.jpg -i onepiece_demo.mp4 -map 0:v:0 -map 1:a:0 -c:a copy -c:v libx264 -r 23.98 -pix_fmt yuv420p output_w_audio.mp4
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
GitHub: https://github.com/xinntao/Real-ESRGAN/
|
||||||
|
Paper: https://arxiv.org/abs/2107.10833
|
||||||
|
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
This executable file is **portable** and includes all the binaries and models required. No CUDA or PyTorch environment is needed.
|
||||||
|
|
||||||
|
Note that it may introduce block inconsistency (and also generate slightly different results from the PyTorch implementation), because this executable file first crops the input image into several tiles, and then processes them separately, finally stitches together.
|
||||||
|
|
||||||
|
This executable file is based on the wonderful [Tencent/ncnn](https://github.com/Tencent/ncnn) and [realsr-ncnn-vulkan](https://github.com/nihui/realsr-ncnn-vulkan) by [nihui](https://github.com/nihui).
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
@@ -0,0 +1,43 @@
|
|||||||
|
7767517
|
||||||
|
41 42
|
||||||
|
Input input.1 0 1 data
|
||||||
|
Split splitncnn_input0 1 2 data input.1_splitncnn_0 input.1_splitncnn_1
|
||||||
|
Convolution Conv_0 1 1 input.1_splitncnn_1 54 0=64 1=3 4=1 5=1 6=1728
|
||||||
|
PReLU PRelu_1 1 1 54 56 0=64
|
||||||
|
Convolution Conv_2 1 1 56 57 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_3 1 1 57 59 0=64
|
||||||
|
Convolution Conv_4 1 1 59 60 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_5 1 1 60 62 0=64
|
||||||
|
Convolution Conv_6 1 1 62 63 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_7 1 1 63 65 0=64
|
||||||
|
Convolution Conv_8 1 1 65 66 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_9 1 1 66 68 0=64
|
||||||
|
Convolution Conv_10 1 1 68 69 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_11 1 1 69 71 0=64
|
||||||
|
Convolution Conv_12 1 1 71 72 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_13 1 1 72 74 0=64
|
||||||
|
Convolution Conv_14 1 1 74 75 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_15 1 1 75 77 0=64
|
||||||
|
Convolution Conv_16 1 1 77 78 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_17 1 1 78 80 0=64
|
||||||
|
Convolution Conv_18 1 1 80 81 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_19 1 1 81 83 0=64
|
||||||
|
Convolution Conv_20 1 1 83 84 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_21 1 1 84 86 0=64
|
||||||
|
Convolution Conv_22 1 1 86 87 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_23 1 1 87 89 0=64
|
||||||
|
Convolution Conv_24 1 1 89 90 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_25 1 1 90 92 0=64
|
||||||
|
Convolution Conv_26 1 1 92 93 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_27 1 1 93 95 0=64
|
||||||
|
Convolution Conv_28 1 1 95 96 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_29 1 1 96 98 0=64
|
||||||
|
Convolution Conv_30 1 1 98 99 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_31 1 1 99 101 0=64
|
||||||
|
Convolution Conv_32 1 1 101 102 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_33 1 1 102 104 0=64
|
||||||
|
Convolution Conv_34 1 1 104 105 0=48 1=3 4=1 5=1 6=27648
|
||||||
|
PixelShuffle DepthToSpace_35 1 1 105 106 0=4
|
||||||
|
Interp Resize_37 1 1 input.1_splitncnn_0 111 0=1 1=4.000000e+00 2=4.000000e+00
|
||||||
|
BinaryOp Add_38 2 1 106 111 112
|
||||||
|
Interp Resize_40 1 1 112 output 0=3 1=5.000000e-01 2=5.000000e-01
|
||||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
|||||||
|
7767517
|
||||||
|
41 42
|
||||||
|
Input input.1 0 1 data
|
||||||
|
Split splitncnn_input0 1 2 data input.1_splitncnn_0 input.1_splitncnn_1
|
||||||
|
Convolution Conv_0 1 1 input.1_splitncnn_1 54 0=64 1=3 4=1 5=1 6=1728
|
||||||
|
PReLU PRelu_1 1 1 54 56 0=64
|
||||||
|
Convolution Conv_2 1 1 56 57 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_3 1 1 57 59 0=64
|
||||||
|
Convolution Conv_4 1 1 59 60 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_5 1 1 60 62 0=64
|
||||||
|
Convolution Conv_6 1 1 62 63 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_7 1 1 63 65 0=64
|
||||||
|
Convolution Conv_8 1 1 65 66 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_9 1 1 66 68 0=64
|
||||||
|
Convolution Conv_10 1 1 68 69 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_11 1 1 69 71 0=64
|
||||||
|
Convolution Conv_12 1 1 71 72 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_13 1 1 72 74 0=64
|
||||||
|
Convolution Conv_14 1 1 74 75 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_15 1 1 75 77 0=64
|
||||||
|
Convolution Conv_16 1 1 77 78 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_17 1 1 78 80 0=64
|
||||||
|
Convolution Conv_18 1 1 80 81 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_19 1 1 81 83 0=64
|
||||||
|
Convolution Conv_20 1 1 83 84 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_21 1 1 84 86 0=64
|
||||||
|
Convolution Conv_22 1 1 86 87 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_23 1 1 87 89 0=64
|
||||||
|
Convolution Conv_24 1 1 89 90 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_25 1 1 90 92 0=64
|
||||||
|
Convolution Conv_26 1 1 92 93 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_27 1 1 93 95 0=64
|
||||||
|
Convolution Conv_28 1 1 95 96 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_29 1 1 96 98 0=64
|
||||||
|
Convolution Conv_30 1 1 98 99 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_31 1 1 99 101 0=64
|
||||||
|
Convolution Conv_32 1 1 101 102 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_33 1 1 102 104 0=64
|
||||||
|
Convolution Conv_34 1 1 104 105 0=48 1=3 4=1 5=1 6=27648
|
||||||
|
PixelShuffle DepthToSpace_35 1 1 105 106 0=4
|
||||||
|
Interp Resize_37 1 1 input.1_splitncnn_0 111 0=1 1=4.000000e+00 2=4.000000e+00
|
||||||
|
BinaryOp Add_38 2 1 106 111 112
|
||||||
|
Interp Resize_40 1 1 112 output 0=3 1=7.500000e-01 2=7.500000e-01
|
||||||
Binary file not shown.
@@ -0,0 +1,42 @@
|
|||||||
|
7767517
|
||||||
|
40 41
|
||||||
|
Input input.1 0 1 data
|
||||||
|
Split splitncnn_input0 1 2 data input.1_splitncnn_0 input.1_splitncnn_1
|
||||||
|
Convolution Conv_0 1 1 input.1_splitncnn_1 54 0=64 1=3 4=1 5=1 6=1728
|
||||||
|
PReLU PRelu_1 1 1 54 56 0=64
|
||||||
|
Convolution Conv_2 1 1 56 57 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_3 1 1 57 59 0=64
|
||||||
|
Convolution Conv_4 1 1 59 60 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_5 1 1 60 62 0=64
|
||||||
|
Convolution Conv_6 1 1 62 63 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_7 1 1 63 65 0=64
|
||||||
|
Convolution Conv_8 1 1 65 66 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_9 1 1 66 68 0=64
|
||||||
|
Convolution Conv_10 1 1 68 69 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_11 1 1 69 71 0=64
|
||||||
|
Convolution Conv_12 1 1 71 72 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_13 1 1 72 74 0=64
|
||||||
|
Convolution Conv_14 1 1 74 75 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_15 1 1 75 77 0=64
|
||||||
|
Convolution Conv_16 1 1 77 78 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_17 1 1 78 80 0=64
|
||||||
|
Convolution Conv_18 1 1 80 81 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_19 1 1 81 83 0=64
|
||||||
|
Convolution Conv_20 1 1 83 84 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_21 1 1 84 86 0=64
|
||||||
|
Convolution Conv_22 1 1 86 87 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_23 1 1 87 89 0=64
|
||||||
|
Convolution Conv_24 1 1 89 90 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_25 1 1 90 92 0=64
|
||||||
|
Convolution Conv_26 1 1 92 93 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_27 1 1 93 95 0=64
|
||||||
|
Convolution Conv_28 1 1 95 96 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_29 1 1 96 98 0=64
|
||||||
|
Convolution Conv_30 1 1 98 99 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_31 1 1 99 101 0=64
|
||||||
|
Convolution Conv_32 1 1 101 102 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
PReLU PRelu_33 1 1 102 104 0=64
|
||||||
|
Convolution Conv_34 1 1 104 105 0=48 1=3 4=1 5=1 6=27648
|
||||||
|
PixelShuffle DepthToSpace_35 1 1 105 106 0=4
|
||||||
|
Interp Resize_37 1 1 input.1_splitncnn_0 111 0=1 1=4.000000e+00 2=4.000000e+00
|
||||||
|
BinaryOp Add_38 2 1 106 111 output
|
||||||
Binary file not shown.
@@ -0,0 +1,270 @@
|
|||||||
|
7767517
|
||||||
|
268 473
|
||||||
|
Input input.1 0 1 data
|
||||||
|
Convolution Conv_0 1 1 data 193 0=64 1=3 4=1 5=1 6=1728
|
||||||
|
Split splitncnn_0 1 8 193 193_splitncnn_0 193_splitncnn_1 193_splitncnn_2 193_splitncnn_3 193_splitncnn_4 193_splitncnn_5 193_splitncnn_6 193_splitncnn_7
|
||||||
|
Convolution Conv_1 1 1 193_splitncnn_7 195 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_1 1 4 195 195_splitncnn_0 195_splitncnn_1 195_splitncnn_2 195_splitncnn_3
|
||||||
|
Concat Concat_3 2 1 193_splitncnn_6 195_splitncnn_3 196
|
||||||
|
Convolution Conv_4 1 1 196 198 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_2 1 3 198 198_splitncnn_0 198_splitncnn_1 198_splitncnn_2
|
||||||
|
Concat Concat_6 3 1 193_splitncnn_5 195_splitncnn_2 198_splitncnn_2 199
|
||||||
|
Convolution Conv_7 1 1 199 201 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_3 1 2 201 201_splitncnn_0 201_splitncnn_1
|
||||||
|
Concat Concat_9 4 1 193_splitncnn_4 195_splitncnn_1 198_splitncnn_1 201_splitncnn_1 202
|
||||||
|
Convolution Conv_10 1 1 202 204 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_12 5 1 193_splitncnn_3 195_splitncnn_0 198_splitncnn_0 201_splitncnn_0 204 205
|
||||||
|
Convolution Conv_13 1 1 205 206 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_16 2 1 206 193_splitncnn_2 209 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_4 1 6 209 209_splitncnn_0 209_splitncnn_1 209_splitncnn_2 209_splitncnn_3 209_splitncnn_4 209_splitncnn_5
|
||||||
|
Convolution Conv_17 1 1 209_splitncnn_5 211 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_5 1 4 211 211_splitncnn_0 211_splitncnn_1 211_splitncnn_2 211_splitncnn_3
|
||||||
|
Concat Concat_19 2 1 209_splitncnn_4 211_splitncnn_3 212
|
||||||
|
Convolution Conv_20 1 1 212 214 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_6 1 3 214 214_splitncnn_0 214_splitncnn_1 214_splitncnn_2
|
||||||
|
Concat Concat_22 3 1 209_splitncnn_3 211_splitncnn_2 214_splitncnn_2 215
|
||||||
|
Convolution Conv_23 1 1 215 217 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_7 1 2 217 217_splitncnn_0 217_splitncnn_1
|
||||||
|
Concat Concat_25 4 1 209_splitncnn_2 211_splitncnn_1 214_splitncnn_1 217_splitncnn_1 218
|
||||||
|
Convolution Conv_26 1 1 218 220 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_28 5 1 209_splitncnn_1 211_splitncnn_0 214_splitncnn_0 217_splitncnn_0 220 221
|
||||||
|
Convolution Conv_29 1 1 221 222 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_32 2 1 222 209_splitncnn_0 225 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_8 1 6 225 225_splitncnn_0 225_splitncnn_1 225_splitncnn_2 225_splitncnn_3 225_splitncnn_4 225_splitncnn_5
|
||||||
|
Convolution Conv_33 1 1 225_splitncnn_5 227 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_9 1 4 227 227_splitncnn_0 227_splitncnn_1 227_splitncnn_2 227_splitncnn_3
|
||||||
|
Concat Concat_35 2 1 225_splitncnn_4 227_splitncnn_3 228
|
||||||
|
Convolution Conv_36 1 1 228 230 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_10 1 3 230 230_splitncnn_0 230_splitncnn_1 230_splitncnn_2
|
||||||
|
Concat Concat_38 3 1 225_splitncnn_3 227_splitncnn_2 230_splitncnn_2 231
|
||||||
|
Convolution Conv_39 1 1 231 233 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_11 1 2 233 233_splitncnn_0 233_splitncnn_1
|
||||||
|
Concat Concat_41 4 1 225_splitncnn_2 227_splitncnn_1 230_splitncnn_1 233_splitncnn_1 234
|
||||||
|
Convolution Conv_42 1 1 234 236 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_44 5 1 225_splitncnn_1 227_splitncnn_0 230_splitncnn_0 233_splitncnn_0 236 237
|
||||||
|
Convolution Conv_45 1 1 237 238 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_48 2 1 238 225_splitncnn_0 241 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Eltwise Add_51 2 1 241 193_splitncnn_1 244 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_12 1 7 244 244_splitncnn_0 244_splitncnn_1 244_splitncnn_2 244_splitncnn_3 244_splitncnn_4 244_splitncnn_5 244_splitncnn_6
|
||||||
|
Convolution Conv_52 1 1 244_splitncnn_6 246 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_13 1 4 246 246_splitncnn_0 246_splitncnn_1 246_splitncnn_2 246_splitncnn_3
|
||||||
|
Concat Concat_54 2 1 244_splitncnn_5 246_splitncnn_3 247
|
||||||
|
Convolution Conv_55 1 1 247 249 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_14 1 3 249 249_splitncnn_0 249_splitncnn_1 249_splitncnn_2
|
||||||
|
Concat Concat_57 3 1 244_splitncnn_4 246_splitncnn_2 249_splitncnn_2 250
|
||||||
|
Convolution Conv_58 1 1 250 252 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_15 1 2 252 252_splitncnn_0 252_splitncnn_1
|
||||||
|
Concat Concat_60 4 1 244_splitncnn_3 246_splitncnn_1 249_splitncnn_1 252_splitncnn_1 253
|
||||||
|
Convolution Conv_61 1 1 253 255 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_63 5 1 244_splitncnn_2 246_splitncnn_0 249_splitncnn_0 252_splitncnn_0 255 256
|
||||||
|
Convolution Conv_64 1 1 256 257 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_67 2 1 257 244_splitncnn_1 260 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_16 1 6 260 260_splitncnn_0 260_splitncnn_1 260_splitncnn_2 260_splitncnn_3 260_splitncnn_4 260_splitncnn_5
|
||||||
|
Convolution Conv_68 1 1 260_splitncnn_5 262 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_17 1 4 262 262_splitncnn_0 262_splitncnn_1 262_splitncnn_2 262_splitncnn_3
|
||||||
|
Concat Concat_70 2 1 260_splitncnn_4 262_splitncnn_3 263
|
||||||
|
Convolution Conv_71 1 1 263 265 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_18 1 3 265 265_splitncnn_0 265_splitncnn_1 265_splitncnn_2
|
||||||
|
Concat Concat_73 3 1 260_splitncnn_3 262_splitncnn_2 265_splitncnn_2 266
|
||||||
|
Convolution Conv_74 1 1 266 268 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_19 1 2 268 268_splitncnn_0 268_splitncnn_1
|
||||||
|
Concat Concat_76 4 1 260_splitncnn_2 262_splitncnn_1 265_splitncnn_1 268_splitncnn_1 269
|
||||||
|
Convolution Conv_77 1 1 269 271 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_79 5 1 260_splitncnn_1 262_splitncnn_0 265_splitncnn_0 268_splitncnn_0 271 272
|
||||||
|
Convolution Conv_80 1 1 272 273 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_83 2 1 273 260_splitncnn_0 276 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_20 1 6 276 276_splitncnn_0 276_splitncnn_1 276_splitncnn_2 276_splitncnn_3 276_splitncnn_4 276_splitncnn_5
|
||||||
|
Convolution Conv_84 1 1 276_splitncnn_5 278 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_21 1 4 278 278_splitncnn_0 278_splitncnn_1 278_splitncnn_2 278_splitncnn_3
|
||||||
|
Concat Concat_86 2 1 276_splitncnn_4 278_splitncnn_3 279
|
||||||
|
Convolution Conv_87 1 1 279 281 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_22 1 3 281 281_splitncnn_0 281_splitncnn_1 281_splitncnn_2
|
||||||
|
Concat Concat_89 3 1 276_splitncnn_3 278_splitncnn_2 281_splitncnn_2 282
|
||||||
|
Convolution Conv_90 1 1 282 284 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_23 1 2 284 284_splitncnn_0 284_splitncnn_1
|
||||||
|
Concat Concat_92 4 1 276_splitncnn_2 278_splitncnn_1 281_splitncnn_1 284_splitncnn_1 285
|
||||||
|
Convolution Conv_93 1 1 285 287 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_95 5 1 276_splitncnn_1 278_splitncnn_0 281_splitncnn_0 284_splitncnn_0 287 288
|
||||||
|
Convolution Conv_96 1 1 288 289 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_99 2 1 289 276_splitncnn_0 292 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Eltwise Add_102 2 1 292 244_splitncnn_0 295 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_24 1 7 295 295_splitncnn_0 295_splitncnn_1 295_splitncnn_2 295_splitncnn_3 295_splitncnn_4 295_splitncnn_5 295_splitncnn_6
|
||||||
|
Convolution Conv_103 1 1 295_splitncnn_6 297 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_25 1 4 297 297_splitncnn_0 297_splitncnn_1 297_splitncnn_2 297_splitncnn_3
|
||||||
|
Concat Concat_105 2 1 295_splitncnn_5 297_splitncnn_3 298
|
||||||
|
Convolution Conv_106 1 1 298 300 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_26 1 3 300 300_splitncnn_0 300_splitncnn_1 300_splitncnn_2
|
||||||
|
Concat Concat_108 3 1 295_splitncnn_4 297_splitncnn_2 300_splitncnn_2 301
|
||||||
|
Convolution Conv_109 1 1 301 303 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_27 1 2 303 303_splitncnn_0 303_splitncnn_1
|
||||||
|
Concat Concat_111 4 1 295_splitncnn_3 297_splitncnn_1 300_splitncnn_1 303_splitncnn_1 304
|
||||||
|
Convolution Conv_112 1 1 304 306 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_114 5 1 295_splitncnn_2 297_splitncnn_0 300_splitncnn_0 303_splitncnn_0 306 307
|
||||||
|
Convolution Conv_115 1 1 307 308 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_118 2 1 308 295_splitncnn_1 311 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_28 1 6 311 311_splitncnn_0 311_splitncnn_1 311_splitncnn_2 311_splitncnn_3 311_splitncnn_4 311_splitncnn_5
|
||||||
|
Convolution Conv_119 1 1 311_splitncnn_5 313 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_29 1 4 313 313_splitncnn_0 313_splitncnn_1 313_splitncnn_2 313_splitncnn_3
|
||||||
|
Concat Concat_121 2 1 311_splitncnn_4 313_splitncnn_3 314
|
||||||
|
Convolution Conv_122 1 1 314 316 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_30 1 3 316 316_splitncnn_0 316_splitncnn_1 316_splitncnn_2
|
||||||
|
Concat Concat_124 3 1 311_splitncnn_3 313_splitncnn_2 316_splitncnn_2 317
|
||||||
|
Convolution Conv_125 1 1 317 319 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_31 1 2 319 319_splitncnn_0 319_splitncnn_1
|
||||||
|
Concat Concat_127 4 1 311_splitncnn_2 313_splitncnn_1 316_splitncnn_1 319_splitncnn_1 320
|
||||||
|
Convolution Conv_128 1 1 320 322 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_130 5 1 311_splitncnn_1 313_splitncnn_0 316_splitncnn_0 319_splitncnn_0 322 323
|
||||||
|
Convolution Conv_131 1 1 323 324 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_134 2 1 324 311_splitncnn_0 327 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_32 1 6 327 327_splitncnn_0 327_splitncnn_1 327_splitncnn_2 327_splitncnn_3 327_splitncnn_4 327_splitncnn_5
|
||||||
|
Convolution Conv_135 1 1 327_splitncnn_5 329 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_33 1 4 329 329_splitncnn_0 329_splitncnn_1 329_splitncnn_2 329_splitncnn_3
|
||||||
|
Concat Concat_137 2 1 327_splitncnn_4 329_splitncnn_3 330
|
||||||
|
Convolution Conv_138 1 1 330 332 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_34 1 3 332 332_splitncnn_0 332_splitncnn_1 332_splitncnn_2
|
||||||
|
Concat Concat_140 3 1 327_splitncnn_3 329_splitncnn_2 332_splitncnn_2 333
|
||||||
|
Convolution Conv_141 1 1 333 335 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_35 1 2 335 335_splitncnn_0 335_splitncnn_1
|
||||||
|
Concat Concat_143 4 1 327_splitncnn_2 329_splitncnn_1 332_splitncnn_1 335_splitncnn_1 336
|
||||||
|
Convolution Conv_144 1 1 336 338 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_146 5 1 327_splitncnn_1 329_splitncnn_0 332_splitncnn_0 335_splitncnn_0 338 339
|
||||||
|
Convolution Conv_147 1 1 339 340 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_150 2 1 340 327_splitncnn_0 343 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Eltwise Add_153 2 1 343 295_splitncnn_0 346 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_36 1 7 346 346_splitncnn_0 346_splitncnn_1 346_splitncnn_2 346_splitncnn_3 346_splitncnn_4 346_splitncnn_5 346_splitncnn_6
|
||||||
|
Convolution Conv_154 1 1 346_splitncnn_6 348 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_37 1 4 348 348_splitncnn_0 348_splitncnn_1 348_splitncnn_2 348_splitncnn_3
|
||||||
|
Concat Concat_156 2 1 346_splitncnn_5 348_splitncnn_3 349
|
||||||
|
Convolution Conv_157 1 1 349 351 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_38 1 3 351 351_splitncnn_0 351_splitncnn_1 351_splitncnn_2
|
||||||
|
Concat Concat_159 3 1 346_splitncnn_4 348_splitncnn_2 351_splitncnn_2 352
|
||||||
|
Convolution Conv_160 1 1 352 354 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_39 1 2 354 354_splitncnn_0 354_splitncnn_1
|
||||||
|
Concat Concat_162 4 1 346_splitncnn_3 348_splitncnn_1 351_splitncnn_1 354_splitncnn_1 355
|
||||||
|
Convolution Conv_163 1 1 355 357 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_165 5 1 346_splitncnn_2 348_splitncnn_0 351_splitncnn_0 354_splitncnn_0 357 358
|
||||||
|
Convolution Conv_166 1 1 358 359 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_169 2 1 359 346_splitncnn_1 362 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_40 1 6 362 362_splitncnn_0 362_splitncnn_1 362_splitncnn_2 362_splitncnn_3 362_splitncnn_4 362_splitncnn_5
|
||||||
|
Convolution Conv_170 1 1 362_splitncnn_5 364 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_41 1 4 364 364_splitncnn_0 364_splitncnn_1 364_splitncnn_2 364_splitncnn_3
|
||||||
|
Concat Concat_172 2 1 362_splitncnn_4 364_splitncnn_3 365
|
||||||
|
Convolution Conv_173 1 1 365 367 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_42 1 3 367 367_splitncnn_0 367_splitncnn_1 367_splitncnn_2
|
||||||
|
Concat Concat_175 3 1 362_splitncnn_3 364_splitncnn_2 367_splitncnn_2 368
|
||||||
|
Convolution Conv_176 1 1 368 370 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_43 1 2 370 370_splitncnn_0 370_splitncnn_1
|
||||||
|
Concat Concat_178 4 1 362_splitncnn_2 364_splitncnn_1 367_splitncnn_1 370_splitncnn_1 371
|
||||||
|
Convolution Conv_179 1 1 371 373 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_181 5 1 362_splitncnn_1 364_splitncnn_0 367_splitncnn_0 370_splitncnn_0 373 374
|
||||||
|
Convolution Conv_182 1 1 374 375 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_185 2 1 375 362_splitncnn_0 378 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_44 1 6 378 378_splitncnn_0 378_splitncnn_1 378_splitncnn_2 378_splitncnn_3 378_splitncnn_4 378_splitncnn_5
|
||||||
|
Convolution Conv_186 1 1 378_splitncnn_5 380 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_45 1 4 380 380_splitncnn_0 380_splitncnn_1 380_splitncnn_2 380_splitncnn_3
|
||||||
|
Concat Concat_188 2 1 378_splitncnn_4 380_splitncnn_3 381
|
||||||
|
Convolution Conv_189 1 1 381 383 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_46 1 3 383 383_splitncnn_0 383_splitncnn_1 383_splitncnn_2
|
||||||
|
Concat Concat_191 3 1 378_splitncnn_3 380_splitncnn_2 383_splitncnn_2 384
|
||||||
|
Convolution Conv_192 1 1 384 386 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_47 1 2 386 386_splitncnn_0 386_splitncnn_1
|
||||||
|
Concat Concat_194 4 1 378_splitncnn_2 380_splitncnn_1 383_splitncnn_1 386_splitncnn_1 387
|
||||||
|
Convolution Conv_195 1 1 387 389 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_197 5 1 378_splitncnn_1 380_splitncnn_0 383_splitncnn_0 386_splitncnn_0 389 390
|
||||||
|
Convolution Conv_198 1 1 390 391 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_201 2 1 391 378_splitncnn_0 394 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Eltwise Add_204 2 1 394 346_splitncnn_0 397 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_48 1 7 397 397_splitncnn_0 397_splitncnn_1 397_splitncnn_2 397_splitncnn_3 397_splitncnn_4 397_splitncnn_5 397_splitncnn_6
|
||||||
|
Convolution Conv_205 1 1 397_splitncnn_6 399 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_49 1 4 399 399_splitncnn_0 399_splitncnn_1 399_splitncnn_2 399_splitncnn_3
|
||||||
|
Concat Concat_207 2 1 397_splitncnn_5 399_splitncnn_3 400
|
||||||
|
Convolution Conv_208 1 1 400 402 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_50 1 3 402 402_splitncnn_0 402_splitncnn_1 402_splitncnn_2
|
||||||
|
Concat Concat_210 3 1 397_splitncnn_4 399_splitncnn_2 402_splitncnn_2 403
|
||||||
|
Convolution Conv_211 1 1 403 405 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_51 1 2 405 405_splitncnn_0 405_splitncnn_1
|
||||||
|
Concat Concat_213 4 1 397_splitncnn_3 399_splitncnn_1 402_splitncnn_1 405_splitncnn_1 406
|
||||||
|
Convolution Conv_214 1 1 406 408 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_216 5 1 397_splitncnn_2 399_splitncnn_0 402_splitncnn_0 405_splitncnn_0 408 409
|
||||||
|
Convolution Conv_217 1 1 409 410 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_220 2 1 410 397_splitncnn_1 413 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_52 1 6 413 413_splitncnn_0 413_splitncnn_1 413_splitncnn_2 413_splitncnn_3 413_splitncnn_4 413_splitncnn_5
|
||||||
|
Convolution Conv_221 1 1 413_splitncnn_5 415 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_53 1 4 415 415_splitncnn_0 415_splitncnn_1 415_splitncnn_2 415_splitncnn_3
|
||||||
|
Concat Concat_223 2 1 413_splitncnn_4 415_splitncnn_3 416
|
||||||
|
Convolution Conv_224 1 1 416 418 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_54 1 3 418 418_splitncnn_0 418_splitncnn_1 418_splitncnn_2
|
||||||
|
Concat Concat_226 3 1 413_splitncnn_3 415_splitncnn_2 418_splitncnn_2 419
|
||||||
|
Convolution Conv_227 1 1 419 421 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_55 1 2 421 421_splitncnn_0 421_splitncnn_1
|
||||||
|
Concat Concat_229 4 1 413_splitncnn_2 415_splitncnn_1 418_splitncnn_1 421_splitncnn_1 422
|
||||||
|
Convolution Conv_230 1 1 422 424 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_232 5 1 413_splitncnn_1 415_splitncnn_0 418_splitncnn_0 421_splitncnn_0 424 425
|
||||||
|
Convolution Conv_233 1 1 425 426 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_236 2 1 426 413_splitncnn_0 429 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_56 1 6 429 429_splitncnn_0 429_splitncnn_1 429_splitncnn_2 429_splitncnn_3 429_splitncnn_4 429_splitncnn_5
|
||||||
|
Convolution Conv_237 1 1 429_splitncnn_5 431 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_57 1 4 431 431_splitncnn_0 431_splitncnn_1 431_splitncnn_2 431_splitncnn_3
|
||||||
|
Concat Concat_239 2 1 429_splitncnn_4 431_splitncnn_3 432
|
||||||
|
Convolution Conv_240 1 1 432 434 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_58 1 3 434 434_splitncnn_0 434_splitncnn_1 434_splitncnn_2
|
||||||
|
Concat Concat_242 3 1 429_splitncnn_3 431_splitncnn_2 434_splitncnn_2 435
|
||||||
|
Convolution Conv_243 1 1 435 437 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_59 1 2 437 437_splitncnn_0 437_splitncnn_1
|
||||||
|
Concat Concat_245 4 1 429_splitncnn_2 431_splitncnn_1 434_splitncnn_1 437_splitncnn_1 438
|
||||||
|
Convolution Conv_246 1 1 438 440 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_248 5 1 429_splitncnn_1 431_splitncnn_0 434_splitncnn_0 437_splitncnn_0 440 441
|
||||||
|
Convolution Conv_249 1 1 441 442 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_252 2 1 442 429_splitncnn_0 445 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Eltwise Add_255 2 1 445 397_splitncnn_0 448 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_60 1 7 448 448_splitncnn_0 448_splitncnn_1 448_splitncnn_2 448_splitncnn_3 448_splitncnn_4 448_splitncnn_5 448_splitncnn_6
|
||||||
|
Convolution Conv_256 1 1 448_splitncnn_6 450 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_61 1 4 450 450_splitncnn_0 450_splitncnn_1 450_splitncnn_2 450_splitncnn_3
|
||||||
|
Concat Concat_258 2 1 448_splitncnn_5 450_splitncnn_3 451
|
||||||
|
Convolution Conv_259 1 1 451 453 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_62 1 3 453 453_splitncnn_0 453_splitncnn_1 453_splitncnn_2
|
||||||
|
Concat Concat_261 3 1 448_splitncnn_4 450_splitncnn_2 453_splitncnn_2 454
|
||||||
|
Convolution Conv_262 1 1 454 456 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_63 1 2 456 456_splitncnn_0 456_splitncnn_1
|
||||||
|
Concat Concat_264 4 1 448_splitncnn_3 450_splitncnn_1 453_splitncnn_1 456_splitncnn_1 457
|
||||||
|
Convolution Conv_265 1 1 457 459 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_267 5 1 448_splitncnn_2 450_splitncnn_0 453_splitncnn_0 456_splitncnn_0 459 460
|
||||||
|
Convolution Conv_268 1 1 460 461 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_271 2 1 461 448_splitncnn_1 464 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_64 1 6 464 464_splitncnn_0 464_splitncnn_1 464_splitncnn_2 464_splitncnn_3 464_splitncnn_4 464_splitncnn_5
|
||||||
|
Convolution Conv_272 1 1 464_splitncnn_5 466 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_65 1 4 466 466_splitncnn_0 466_splitncnn_1 466_splitncnn_2 466_splitncnn_3
|
||||||
|
Concat Concat_274 2 1 464_splitncnn_4 466_splitncnn_3 467
|
||||||
|
Convolution Conv_275 1 1 467 469 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_66 1 3 469 469_splitncnn_0 469_splitncnn_1 469_splitncnn_2
|
||||||
|
Concat Concat_277 3 1 464_splitncnn_3 466_splitncnn_2 469_splitncnn_2 470
|
||||||
|
Convolution Conv_278 1 1 470 472 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_67 1 2 472 472_splitncnn_0 472_splitncnn_1
|
||||||
|
Concat Concat_280 4 1 464_splitncnn_2 466_splitncnn_1 469_splitncnn_1 472_splitncnn_1 473
|
||||||
|
Convolution Conv_281 1 1 473 475 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_283 5 1 464_splitncnn_1 466_splitncnn_0 469_splitncnn_0 472_splitncnn_0 475 476
|
||||||
|
Convolution Conv_284 1 1 476 477 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_287 2 1 477 464_splitncnn_0 480 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Split splitncnn_68 1 6 480 480_splitncnn_0 480_splitncnn_1 480_splitncnn_2 480_splitncnn_3 480_splitncnn_4 480_splitncnn_5
|
||||||
|
Convolution Conv_288 1 1 480_splitncnn_5 482 0=32 1=3 4=1 5=1 6=18432 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_69 1 4 482 482_splitncnn_0 482_splitncnn_1 482_splitncnn_2 482_splitncnn_3
|
||||||
|
Concat Concat_290 2 1 480_splitncnn_4 482_splitncnn_3 483
|
||||||
|
Convolution Conv_291 1 1 483 485 0=32 1=3 4=1 5=1 6=27648 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_70 1 3 485 485_splitncnn_0 485_splitncnn_1 485_splitncnn_2
|
||||||
|
Concat Concat_293 3 1 480_splitncnn_3 482_splitncnn_2 485_splitncnn_2 486
|
||||||
|
Convolution Conv_294 1 1 486 488 0=32 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Split splitncnn_71 1 2 488 488_splitncnn_0 488_splitncnn_1
|
||||||
|
Concat Concat_296 4 1 480_splitncnn_2 482_splitncnn_1 485_splitncnn_1 488_splitncnn_1 489
|
||||||
|
Convolution Conv_297 1 1 489 491 0=32 1=3 4=1 5=1 6=46080 9=2 -23310=1,2.000000e-01
|
||||||
|
Concat Concat_299 5 1 480_splitncnn_1 482_splitncnn_0 485_splitncnn_0 488_splitncnn_0 491 492
|
||||||
|
Convolution Conv_300 1 1 492 493 0=64 1=3 4=1 5=1 6=110592
|
||||||
|
Eltwise Add_303 2 1 493 480_splitncnn_0 496 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Eltwise Add_306 2 1 496 448_splitncnn_0 499 0=1 -23301=2,2.000000e-01,1.000000e+00
|
||||||
|
Convolution Conv_307 1 1 499 500 0=64 1=3 4=1 5=1 6=36864
|
||||||
|
BinaryOp Add_308 2 1 193_splitncnn_0 500 501
|
||||||
|
Interp Resize_310 1 1 501 506 0=1 1=2.000000e+00 2=2.000000e+00
|
||||||
|
Convolution Conv_311 1 1 506 508 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Interp Resize_314 1 1 508 513 0=1 1=2.000000e+00 2=2.000000e+00
|
||||||
|
Convolution Conv_315 1 1 513 515 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Convolution Conv_317 1 1 515 517 0=64 1=3 4=1 5=1 6=36864 9=2 -23310=1,2.000000e-01
|
||||||
|
Convolution Conv_319 1 1 517 output 0=3 1=3 4=1 5=1 6=1728
|
||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,9 @@
|
|||||||
|
# AI Video Upscaler — Python Dependencies
|
||||||
|
# Install: pip install -r requirements.txt
|
||||||
|
|
||||||
|
fastapi>=0.115.0
|
||||||
|
uvicorn>=0.30.0
|
||||||
|
python-multipart>=0.0.9
|
||||||
|
websockets>=13.0
|
||||||
|
pydantic>=2.0
|
||||||
|
torch>=2.0
|
||||||
Executable
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
restart.py — Restart the AI Video Upscaler web application.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python restart.py # Restart on default port 8000
|
||||||
|
python restart.py --port 9000 # Restart on a custom port
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Import siblings
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
sys.path.insert(0, BASE_DIR)
|
||||||
|
|
||||||
|
from stop import stop
|
||||||
|
from start import start
|
||||||
|
|
||||||
|
|
||||||
|
def restart(host: str, port: int, open_browser: bool = True) -> None:
|
||||||
|
stop()
|
||||||
|
# Brief pause to let the port fully release
|
||||||
|
time.sleep(1)
|
||||||
|
start(host, port, open_browser=open_browser)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Restart the AI Video Upscaler")
|
||||||
|
parser.add_argument("--host", default="0.0.0.0",
|
||||||
|
help="Bind address (default: 0.0.0.0 to bind all interfaces, use 127.0.0.1 for local loopback only)")
|
||||||
|
parser.add_argument("--port", type=int, default=8000,
|
||||||
|
help="Port to listen on (default: 8000)")
|
||||||
|
parser.add_argument("--no-browser", action="store_true",
|
||||||
|
help="Don't open the browser automatically")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
restart(args.host, args.port, open_browser=not args.no_browser)
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ── setup.sh — AI Video Upscaler environment setup ──────────────────────────
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# chmod +x setup.sh && ./setup.sh
|
||||||
|
#
|
||||||
|
# Automates first-time setup on a new system:
|
||||||
|
# • Verifies required system tools (python3, ffmpeg, ffprobe)
|
||||||
|
# • Warns if Vulkan is unavailable
|
||||||
|
# • Creates a Python virtual environment and installs dependencies
|
||||||
|
# • Makes the Real-ESRGAN binary executable
|
||||||
|
# • Creates working directories (uploads, outputs, temp)
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ── Colors & Helpers ─────────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${CYAN}ℹ $*${RESET}"; }
|
||||||
|
ok() { echo -e "${GREEN}✅ $*${RESET}"; }
|
||||||
|
warn() { echo -e "${YELLOW}⚠ $*${RESET}"; }
|
||||||
|
fail() { echo -e "${RED}❌ $*${RESET}"; exit 1; }
|
||||||
|
step() { echo -e "\n${BOLD}── $* ──${RESET}"; }
|
||||||
|
|
||||||
|
# ── Banner ───────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}🔧 AI Video Upscaler — Setup${RESET}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Script Directory ─────────────────────────────────────────────────────────
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
# ── 1. Check: python3 ───────────────────────────────────────────────────────
|
||||||
|
step "Checking dependencies"
|
||||||
|
|
||||||
|
if ! command -v python3 &>/dev/null; then
|
||||||
|
fail "python3 is not installed.\n Install it with your package manager (e.g. apt install python3, dnf install python3)."
|
||||||
|
fi
|
||||||
|
ok "python3 found — $(python3 --version)"
|
||||||
|
|
||||||
|
# ── 2. Check: ffmpeg ────────────────────────────────────────────────────────
|
||||||
|
if ! command -v ffmpeg &>/dev/null; then
|
||||||
|
fail "ffmpeg is not installed.\n Install it with your package manager:\n • Debian/Ubuntu : sudo apt install ffmpeg\n • Fedora/RHEL : sudo dnf install ffmpeg\n • Arch : sudo pacman -S ffmpeg"
|
||||||
|
fi
|
||||||
|
ok "ffmpeg found — $(ffmpeg -version | head -1)"
|
||||||
|
|
||||||
|
# ── 3. Check: ffprobe ───────────────────────────────────────────────────────
|
||||||
|
if ! command -v ffprobe &>/dev/null; then
|
||||||
|
fail "ffprobe is not installed (usually bundled with ffmpeg).\n Reinstall ffmpeg to get it."
|
||||||
|
fi
|
||||||
|
ok "ffprobe found"
|
||||||
|
|
||||||
|
# ── 4. Check: vulkaninfo (optional) ─────────────────────────────────────────
|
||||||
|
if ! command -v vulkaninfo &>/dev/null; then
|
||||||
|
warn "vulkaninfo not found — Vulkan GPU acceleration is recommended but not required."
|
||||||
|
warn "Install vulkan-tools for GPU-accelerated upscaling."
|
||||||
|
else
|
||||||
|
ok "vulkaninfo found — Vulkan support detected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 5. Create Python venv ───────────────────────────────────────────────────
|
||||||
|
step "Setting up Python virtual environment"
|
||||||
|
|
||||||
|
if [ -d "$SCRIPT_DIR/venv" ]; then
|
||||||
|
info "venv already exists at $SCRIPT_DIR/venv — skipping creation."
|
||||||
|
else
|
||||||
|
info "Creating venv at $SCRIPT_DIR/venv …"
|
||||||
|
python3 -m venv "$SCRIPT_DIR/venv"
|
||||||
|
ok "Virtual environment created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 6. Install pip dependencies ─────────────────────────────────────────────
|
||||||
|
step "Installing Python dependencies"
|
||||||
|
|
||||||
|
info "Running pip install -r requirements.txt …"
|
||||||
|
"$SCRIPT_DIR/venv/bin/pip" install --upgrade pip --quiet
|
||||||
|
"$SCRIPT_DIR/venv/bin/pip" install -r "$SCRIPT_DIR/requirements.txt"
|
||||||
|
ok "All Python dependencies installed"
|
||||||
|
|
||||||
|
# ── 7. Make Real-ESRGAN binary executable ────────────────────────────────────
|
||||||
|
step "Configuring Real-ESRGAN binary"
|
||||||
|
|
||||||
|
if [ -f "$SCRIPT_DIR/realesrgan-bin/realesrgan-ncnn-vulkan" ]; then
|
||||||
|
chmod +x "$SCRIPT_DIR/realesrgan-bin/realesrgan-ncnn-vulkan"
|
||||||
|
ok "realesrgan-ncnn-vulkan marked as executable"
|
||||||
|
else
|
||||||
|
warn "realesrgan-ncnn-vulkan binary not found at $SCRIPT_DIR/realesrgan-bin/"
|
||||||
|
warn "You may need to download it manually."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 8. Create working directories ───────────────────────────────────────────
|
||||||
|
step "Creating working directories"
|
||||||
|
|
||||||
|
for dir in uploads outputs temp; do
|
||||||
|
mkdir -p "$SCRIPT_DIR/$dir"
|
||||||
|
done
|
||||||
|
ok "Directories ready: uploads/ outputs/ temp/"
|
||||||
|
|
||||||
|
# ── Done ─────────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}${BOLD}────────────────────────────────────────────${RESET}"
|
||||||
|
echo -e "${GREEN}${BOLD} ✅ Setup complete!${RESET}"
|
||||||
|
echo -e "${GREEN}${BOLD}────────────────────────────────────────────${RESET}"
|
||||||
|
echo ""
|
||||||
|
echo -e " Launch the server with:"
|
||||||
|
echo -e " ${CYAN}python start.py${RESET}"
|
||||||
|
echo ""
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
start.py — Start the AI Video Upscaler web application.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python start.py # Start on default port 8000 (binds to 0.0.0.0 for LAN access)
|
||||||
|
python start.py --port 9000 # Start on a custom port
|
||||||
|
python start.py --host 127.0.0.1 # Bind to loopback only (no LAN access)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
# ── Paths ────────────────────────────────────────────────────────────────────
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PID_FILE = os.path.join(BASE_DIR, ".uvicorn.pid")
|
||||||
|
LOG_FILE = os.path.join(BASE_DIR, "server.log")
|
||||||
|
# Detect the right Python interpreter:
|
||||||
|
# 1. If running inside an activated venv, use that interpreter
|
||||||
|
# 2. Otherwise look for venv/ then .venv/ in the project
|
||||||
|
if sys.prefix != sys.base_prefix:
|
||||||
|
# Already inside an activated venv — use it
|
||||||
|
VENV_PYTHON = sys.executable
|
||||||
|
else:
|
||||||
|
VENV_PYTHON = os.path.join(BASE_DIR, "venv", "bin", "python")
|
||||||
|
if not os.path.isfile(VENV_PYTHON):
|
||||||
|
VENV_PYTHON = os.path.join(BASE_DIR, ".venv", "bin", "python")
|
||||||
|
if not os.path.isfile(VENV_PYTHON):
|
||||||
|
VENV_PYTHON = sys.executable # last resort: system python
|
||||||
|
|
||||||
|
|
||||||
|
def _is_running(pid: int) -> bool:
|
||||||
|
"""Check whether a process with the given PID is still alive and is our server."""
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check /proc/{pid}/cmdline to verify it's our server process
|
||||||
|
try:
|
||||||
|
cmdline_path = f"/proc/{pid}/cmdline"
|
||||||
|
if os.path.exists(cmdline_path):
|
||||||
|
with open(cmdline_path, "r") as f:
|
||||||
|
cmdline = f.read()
|
||||||
|
# Check for indicators of our server
|
||||||
|
return "uvicorn" in cmdline or "app.main" in cmdline
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# If we can't read cmdline, default to True (safer fallback)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _read_pid() -> int | None:
|
||||||
|
"""Read and validate the PID from the pidfile."""
|
||||||
|
if not os.path.isfile(PID_FILE):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
pid = int(open(PID_FILE).read().strip())
|
||||||
|
return pid if _is_running(pid) else None
|
||||||
|
except (ValueError, OSError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _get_local_ip() -> str:
|
||||||
|
"""Get the primary local network IP address."""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
# Dummy connection to google DNS to get local route IP
|
||||||
|
s.connect(("8.8.8.8", 80))
|
||||||
|
ip = s.getsockname()[0]
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
ip = socket.gethostbyname(socket.gethostname())
|
||||||
|
except Exception:
|
||||||
|
ip = "127.0.0.1"
|
||||||
|
finally:
|
||||||
|
s.close()
|
||||||
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
def start(host: str, port: int, open_browser: bool = True) -> None:
|
||||||
|
existing = _read_pid()
|
||||||
|
if existing:
|
||||||
|
print(f"⚠ Server is already running (PID {existing}).")
|
||||||
|
if host == "0.0.0.0":
|
||||||
|
local_ip = _get_local_ip()
|
||||||
|
print(f" → Local: http://localhost:{port}")
|
||||||
|
if local_ip and local_ip != "127.0.0.1":
|
||||||
|
print(f" → Network: http://{local_ip}:{port}")
|
||||||
|
else:
|
||||||
|
print(f" → http://{host}:{port}")
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🚀 Starting AI Video Upscaler …")
|
||||||
|
print(f" Host : {host}")
|
||||||
|
print(f" Port : {port}")
|
||||||
|
print(f" Log : {LOG_FILE}")
|
||||||
|
|
||||||
|
log_fh = open(LOG_FILE, "a")
|
||||||
|
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
[
|
||||||
|
VENV_PYTHON, "-m", "uvicorn",
|
||||||
|
"app.main:app",
|
||||||
|
"--host", host,
|
||||||
|
"--port", str(port),
|
||||||
|
],
|
||||||
|
cwd=BASE_DIR,
|
||||||
|
stdout=log_fh,
|
||||||
|
stderr=log_fh,
|
||||||
|
start_new_session=True, # detach from terminal
|
||||||
|
)
|
||||||
|
|
||||||
|
# Write PID
|
||||||
|
with open(PID_FILE, "w") as f:
|
||||||
|
f.write(str(proc.pid))
|
||||||
|
|
||||||
|
# Wait briefly to make sure it didn't crash immediately
|
||||||
|
time.sleep(1.5)
|
||||||
|
if proc.poll() is not None:
|
||||||
|
print("❌ Server failed to start. Check server.log for details.")
|
||||||
|
os.remove(PID_FILE)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
url = f"http://{'localhost' if host == '0.0.0.0' else host}:{port}"
|
||||||
|
print(f"✅ Server started (PID {proc.pid})")
|
||||||
|
if host == "0.0.0.0":
|
||||||
|
local_ip = _get_local_ip()
|
||||||
|
print(f" → Local: http://localhost:{port}")
|
||||||
|
if local_ip and local_ip != "127.0.0.1":
|
||||||
|
print(f" → Network: http://{local_ip}:{port}")
|
||||||
|
else:
|
||||||
|
print(f" → {url}")
|
||||||
|
|
||||||
|
if open_browser:
|
||||||
|
webbrowser.open(url)
|
||||||
|
|
||||||
|
|
||||||
|
# ── CLI ──────────────────────────────────────────────────────────────────────
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Start the AI Video Upscaler")
|
||||||
|
parser.add_argument("--host", default="0.0.0.0",
|
||||||
|
help="Bind address (default: 0.0.0.0 to bind all interfaces, use 127.0.0.1 for local loopback only)")
|
||||||
|
parser.add_argument("--port", type=int, default=8000,
|
||||||
|
help="Port to listen on (default: 8000)")
|
||||||
|
parser.add_argument("--no-browser", action="store_true",
|
||||||
|
help="Don't open the browser automatically")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
start(args.host, args.port, open_browser=not args.no_browser)
|
||||||
+1483
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,576 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Antigravity AI Video Upscaler</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<!-- FontAwesome for modern icons -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- Dashboard Header -->
|
||||||
|
<header>
|
||||||
|
<div class="logo-container">
|
||||||
|
<div class="logo-icon">▲</div>
|
||||||
|
<div class="logo-text">
|
||||||
|
<h1>ANTIGRAVITY // UPSCALE</h1>
|
||||||
|
<p>Open Source AI Video Enhancer</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-actions">
|
||||||
|
<div class="theme-selector">
|
||||||
|
<label for="theme-select"><i class="fa-solid fa-palette"></i> Theme</label>
|
||||||
|
<select id="theme-select">
|
||||||
|
<option value="cyberpunk" selected>Cyberpunk Neon</option>
|
||||||
|
<option value="oled">OLED Obsidian</option>
|
||||||
|
<option value="slate">Slate Gray</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="gpu-badge">
|
||||||
|
<div class="gpu-dot"></div>
|
||||||
|
<span id="gpu-info">GPU Active: Quadro P2000 (Vulkan)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- Step 1: Upload Video -->
|
||||||
|
<div id="step-upload" class="step-container active">
|
||||||
|
<div class="glass-card">
|
||||||
|
<div id="drop-zone" class="upload-area">
|
||||||
|
<input type="file" id="file-input" accept="video/*" style="display: none;">
|
||||||
|
<span class="upload-icon"><i class="fa-solid fa-cloud-arrow-up"></i></span>
|
||||||
|
<h3>Drag & Drop Video File</h3>
|
||||||
|
<p>Supports MP4, MKV, AVI, MOV, WEBM (Max 500MB recommended for local upscaling)</p>
|
||||||
|
<button type="button" class="btn" onclick="document.getElementById('file-input').click()">
|
||||||
|
<i class="fa-solid fa-folder-open"></i> Browse Files
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 2: Configuration & Preview -->
|
||||||
|
<div id="step-config" class="step-container">
|
||||||
|
<div class="config-layout">
|
||||||
|
<!-- Config Sidebar -->
|
||||||
|
<div class="config-sidebar">
|
||||||
|
<!-- Video Metadata Display -->
|
||||||
|
<div class="glass-card meta-panel">
|
||||||
|
<h4>Video Source Info</h4>
|
||||||
|
<div class="meta-grid">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>File Name</span>
|
||||||
|
<span id="meta-name" class="text-truncate">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Size</span>
|
||||||
|
<span id="meta-size">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Detailed Streams Accordion -->
|
||||||
|
<div class="streams-accordions">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<div class="accordion-header" onclick="this.parentElement.classList.toggle('open')">
|
||||||
|
<span><i class="fa-solid fa-video"></i> Video Stream #0</span>
|
||||||
|
<i class="fa-solid fa-chevron-down toggle-icon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-content">
|
||||||
|
<div class="secondary-grid">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Codec</span>
|
||||||
|
<span id="meta-res-codec">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Resolution</span>
|
||||||
|
<span id="meta-res">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Frame Rate</span>
|
||||||
|
<span id="meta-fps">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Duration</span>
|
||||||
|
<span id="meta-duration">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Total Frames</span>
|
||||||
|
<span id="meta-total-frames">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Aspect Ratio</span>
|
||||||
|
<span id="meta-aspect-ratio">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<div class="accordion-header" onclick="this.parentElement.classList.toggle('open')">
|
||||||
|
<span><i class="fa-solid fa-volume-high"></i> Audio Stream #1</span>
|
||||||
|
<i class="fa-solid fa-chevron-down toggle-icon"></i>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-content">
|
||||||
|
<div class="secondary-grid">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Codec</span>
|
||||||
|
<span id="meta-audio-codec">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Channels</span>
|
||||||
|
<span id="meta-audio-channels">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Sample Rate</span>
|
||||||
|
<span id="meta-audio-samplerate">-</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span>Bitrate</span>
|
||||||
|
<span id="meta-audio-bitrate">-</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Trimming Controls Panel -->
|
||||||
|
<div class="glass-card trimming-panel">
|
||||||
|
<h4 style="font-family: var(--font-heading); color: var(--primary); font-size: 1.1rem; margin-bottom: 0.5rem;"><i class="fa-solid fa-scissors"></i> Video Trimming</h4>
|
||||||
|
<p style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.75rem;">Specify range to upscale only a portion of the video.</p>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="trim-start-range">Trim Start: <span id="trim-start-value" class="badge">0.00s</span></label>
|
||||||
|
<div class="range-slider">
|
||||||
|
<input type="range" id="trim-start-range" min="0" max="100" step="0.05" value="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group" style="margin-top: 1rem;">
|
||||||
|
<label for="trim-end-range">Trim End: <span id="trim-end-value" class="badge">10.00s</span></label>
|
||||||
|
<div class="range-slider">
|
||||||
|
<input type="range" id="trim-end-range" min="0" max="100" step="0.05" value="10">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Config Parameters Form -->
|
||||||
|
<div class="glass-card settings-panel">
|
||||||
|
<h4 style="font-family: var(--font-heading); color: var(--primary);">Upscaling Profile</h4>
|
||||||
|
|
||||||
|
<!-- Model Selection -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="model-select">AI Upscaling Model</label>
|
||||||
|
<select id="model-select">
|
||||||
|
<option value="realesr-animevideov3">Real-ESRGAN Anime Video V3 (Fast / Anime & Video)</option>
|
||||||
|
<option value="realesrgan-x4plus" selected>Real-ESRGAN X4 Plus (Ultra Quality / Real World)</option>
|
||||||
|
<option value="realesrgan-x4plus-anime">Real-ESRGAN X4 Plus Anime (Ultra Quality / Illustration)</option>
|
||||||
|
<option value="realesrnet-x4plus">Real-ESRNet X4 Plus (Alternate / Real World)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scale Factor -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="scale-range">Scale Factor <span id="scale-value" class="badge">4x</span></label>
|
||||||
|
<div class="range-slider">
|
||||||
|
<input type="range" id="scale-range" min="2" max="4" step="1" value="4">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Advanced Settings Toggle Header -->
|
||||||
|
<div class="advanced-settings-header" onclick="this.classList.toggle('open'); document.getElementById('advanced-settings-content').classList.toggle('open')">
|
||||||
|
<span><i class="fa-solid fa-sliders"></i> Advanced Options</span>
|
||||||
|
<i class="fa-solid fa-chevron-down"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Collapsible Content -->
|
||||||
|
<div id="advanced-settings-content" class="advanced-settings-content">
|
||||||
|
<!-- GPU Device Selection -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="gpu-select">Vulkan GPU Selector</label>
|
||||||
|
<select id="gpu-select">
|
||||||
|
<option value="auto" selected>Auto Detect Device</option>
|
||||||
|
<option value="0">GPU Device #0</option>
|
||||||
|
<option value="1">GPU Device #1</option>
|
||||||
|
<option value="2">GPU Device #2</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- GPU Tile Size -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tile-range">GPU Tile Size <span id="tile-value" class="badge">256</span></label>
|
||||||
|
<div class="range-slider">
|
||||||
|
<input type="range" id="tile-range" min="64" max="512" step="64" value="256">
|
||||||
|
</div>
|
||||||
|
<p style="font-size: 0.72rem; color: var(--text-muted); margin-top: 2px;">
|
||||||
|
Halves automatically in case of Vulkan VRAM allocation faults.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CRF Quality Slider -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="crf-range">Constant Rate Factor (CRF) <span id="crf-value" class="badge">18</span></label>
|
||||||
|
<div class="range-slider">
|
||||||
|
<input type="range" id="crf-range" min="0" max="51" step="1" value="18">
|
||||||
|
</div>
|
||||||
|
<p style="font-size: 0.72rem; color: var(--text-muted); margin-top: 2px;">
|
||||||
|
0 (lossless) to 51 (worst). Default 18 (visually pristine).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Speed/Quality Preset -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="preset-select">FFmpeg H.264 Encoder Preset</label>
|
||||||
|
<select id="preset-select">
|
||||||
|
<option value="ultrafast">Ultrafast (High Speed / Large Size)</option>
|
||||||
|
<option value="superfast">Superfast</option>
|
||||||
|
<option value="veryfast">Veryfast</option>
|
||||||
|
<option value="faster">Faster</option>
|
||||||
|
<option value="fast">Fast</option>
|
||||||
|
<option value="medium" selected>Medium (Balanced)</option>
|
||||||
|
<option value="slow">Slow (Max H.264 Quality Compression)</option>
|
||||||
|
<option value="slower">Slower</option>
|
||||||
|
<option value="veryslow">Veryslow (Maximum Compression)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Output Video container -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="transcode-select">Output Container Format</label>
|
||||||
|
<select id="transcode-select">
|
||||||
|
<option value="mp4" selected>MP4 (MPEG-4 H.264 Wrapper)</option>
|
||||||
|
<option value="mkv">MKV (Matroska Video Container)</option>
|
||||||
|
<option value="mov">MOV (QuickTime Video Wrapper)</option>
|
||||||
|
<option value="avi">AVI (Legacy Audio Video Interleave)</option>
|
||||||
|
<option value="webm">WebM (VP9 / Web Native HTML5)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pre-filters selection checkboxes -->
|
||||||
|
<div class="filter-group">
|
||||||
|
<span style="font-weight: 600; font-size: 0.85rem; display: block; color: var(--primary);">Pre-Filters & Enhancing</span>
|
||||||
|
<div class="filter-checkboxes">
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" id="filter-denoise"> Denoise (hqdn3d)
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" id="filter-sharpen"> Sharpen (unsharp)
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-container">
|
||||||
|
<input type="checkbox" id="filter-fps"> Double FPS (60FPS Interpolate)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Webhook Notify Input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="webhook-url">Webhook Notification Callback</label>
|
||||||
|
<input type="text" id="webhook-url" placeholder="https://your-server.com/webhook" style="background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-color); color: var(--text-main); padding: 0.8rem 1rem; border-radius: 10px; font-family: var(--font-body); font-size: 0.95rem; outline: none; transition: border 0.3s; width: 100%;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom Model Upload Input -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Upload Custom Model (.param + .bin)</label>
|
||||||
|
<div class="model-upload-zone" onclick="document.getElementById('custom-model-input').click()">
|
||||||
|
<input type="file" id="custom-model-input" accept=".param,.bin" multiple style="display: none;">
|
||||||
|
<i class="fa-solid fa-cubes-stacked"></i>
|
||||||
|
<p>Select both model files to load into realesrgan</p>
|
||||||
|
<span id="model-upload-filename" style="font-size: 0.72rem; color: var(--primary); font-weight: bold; word-break: break-all;">No files selected</span>
|
||||||
|
</div>
|
||||||
|
<div class="upload-model-btn-container" style="display: none;" id="upload-model-btn-wrapper">
|
||||||
|
<button type="button" class="btn btn-secondary btn-small" id="upload-model-btn" style="width: 100%;">
|
||||||
|
<i class="fa-solid fa-cloud-arrow-up"></i> Upload Model Files
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Preserve Audio Toggle -->
|
||||||
|
<div class="toggle-container">
|
||||||
|
<div>
|
||||||
|
<span style="font-weight: 600; font-size: 0.9rem; display: block;">Preserve Audio</span>
|
||||||
|
<span style="font-size: 0.75rem; color: var(--text-muted);">Copy audio streams into upscaled video</span>
|
||||||
|
</div>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="audio-toggle" checked>
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<button type="button" id="start-upscale-btn" class="btn" style="margin-top: 1rem;">
|
||||||
|
<i class="fa-solid fa-play"></i> Start Full Upscale
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Comparison Preview Area -->
|
||||||
|
<div class="glass-card preview-panel">
|
||||||
|
<div class="preview-header">
|
||||||
|
<h3>Clarity Preview</h3>
|
||||||
|
<button type="button" id="prev-btn" class="btn btn-secondary" onclick="window.location.reload()">
|
||||||
|
<i class="fa-solid fa-arrow-left"></i> Change Video
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Timeline Selection -->
|
||||||
|
<div class="timeline-picker">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 0.25rem;">
|
||||||
|
<label for="timeline-slider" style="margin: 0; padding-bottom: 0;">
|
||||||
|
Select Frame Position: <span id="timeline-time" style="color: var(--primary); font-weight: bold;">0.00s</span>
|
||||||
|
</label>
|
||||||
|
<div style="display: flex; gap: 0.5rem;">
|
||||||
|
<button type="button" id="generate-preview-btn" class="btn btn-secondary btn-small" style="padding: 0.4rem 1rem !important;">
|
||||||
|
<i class="fa-solid fa-wand-magic-sparkles"></i> Frame Preview
|
||||||
|
</button>
|
||||||
|
<button type="button" id="generate-video-preview-btn" class="btn btn-primary btn-small" style="padding: 0.4rem 1rem !important;">
|
||||||
|
<i class="fa-solid fa-video"></i> 5s Video Preview
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="range" id="timeline-slider" min="0" max="100" step="0.1" value="10">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Interactive Comparison Container -->
|
||||||
|
<div style="position: relative;">
|
||||||
|
<!-- Placeholder -->
|
||||||
|
<div id="preview-placeholder-box" class="preview-placeholder">
|
||||||
|
<i class="fa-solid fa-image-portrait"></i>
|
||||||
|
<h4>Clarity Preview Frame</h4>
|
||||||
|
<p>Extracts a single frame at the chosen timestamp and runs the AI model to show side-by-side comparative clarity enhancement.</p>
|
||||||
|
<p style="font-size: 0.82rem; color: var(--text-muted); margin-top: 0.5rem;">Click "Render Preview Frame" above to generate the comparative view.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Interactive Slider (Hidden by default) -->
|
||||||
|
<div id="comparison-slider-container" class="comparison-wrapper" style="display: none;">
|
||||||
|
<img id="img-original" class="comparison-image original" src="" alt="Before Upscaling" style="--zoom: 1; --pan-x: 0px; --pan-y: 0px;">
|
||||||
|
<div id="img-upscaled-wrapper" class="upscaled-wrapper">
|
||||||
|
<img id="img-upscaled" class="comparison-image upscaled" src="" alt="After Upscaling" style="--zoom: 1; --pan-x: 0px; --pan-y: 0px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Video Elements for Synced Video Preview -->
|
||||||
|
<video id="video-preview-original" class="comparison-image original" loop muted playsinline style="display: none; --zoom: 1; --pan-x: 0px; --pan-y: 0px;"></video>
|
||||||
|
<div id="video-preview-upscaled-wrapper" class="upscaled-wrapper" style="display: none;">
|
||||||
|
<video id="video-preview-upscaled" class="comparison-image upscaled" loop muted playsinline style="--zoom: 1; --pan-x: 0px; --pan-y: 0px;"></video>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="slider-split" class="slider-bar">
|
||||||
|
<div class="slider-handle"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="badge-orig">Before (Low Res)</span>
|
||||||
|
<span class="badge-up">After (AI Upscaled)</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Zoom & Pan Controls -->
|
||||||
|
<div id="zoom-pan-controls" class="zoom-pan-bar" style="display: none;">
|
||||||
|
<div class="zoom-control-item">
|
||||||
|
<label for="zoom-slider">Zoom: <span id="zoom-val">1.0x</span></label>
|
||||||
|
<input type="range" id="zoom-slider" min="1" max="5" step="0.1" value="1">
|
||||||
|
</div>
|
||||||
|
<div class="zoom-control-item">
|
||||||
|
<label for="pan-x-slider">Pan X: <span id="pan-x-val">0px</span></label>
|
||||||
|
<input type="range" id="pan-x-slider" min="-300" max="300" step="1" value="0">
|
||||||
|
</div>
|
||||||
|
<div class="zoom-control-item">
|
||||||
|
<label for="pan-y-slider">Pan Y: <span id="pan-y-val">0px</span></label>
|
||||||
|
<input type="range" id="pan-y-slider" min="-300" max="300" step="1" value="0">
|
||||||
|
</div>
|
||||||
|
<button type="button" id="reset-zoom-pan-btn" class="btn btn-secondary btn-small">
|
||||||
|
<i class="fa-solid fa-arrows-to-eye"></i> Reset
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Video Preview Playback Controls -->
|
||||||
|
<div id="video-preview-controls" class="zoom-pan-bar" style="display: none; justify-content: center; align-items: center; gap: 1rem; margin-top: 1rem;">
|
||||||
|
<button type="button" id="vprep-play-btn" class="btn btn-primary btn-circular">
|
||||||
|
<i class="fa-solid fa-play"></i>
|
||||||
|
</button>
|
||||||
|
<div class="time-scrubber-container">
|
||||||
|
<span id="vprep-time-current">0:00</span>
|
||||||
|
<input type="range" id="vprep-scrubber" min="0" max="5" step="0.05" value="0">
|
||||||
|
<span id="vprep-time-duration">0:05</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loader overlay -->
|
||||||
|
<div id="preview-loader" class="preview-loading">
|
||||||
|
<div class="spinner" style="margin-bottom: 1rem;"></div>
|
||||||
|
<h4 id="preview-loader-title" style="font-family: var(--font-heading); margin-bottom: 0.5rem;">Orchestrating Vulkan GPU...</h4>
|
||||||
|
<div id="preview-loader-progress-container" style="width: 100%; max-width: 250px; height: 6px; background: rgba(255,255,255,0.1); border-radius: 3px; overflow: hidden; margin: 0.5rem 0; display: none;">
|
||||||
|
<div id="preview-loader-progress-bar" style="width: 0%; height: 100%; background: var(--primary); transition: width 0.3s ease;"></div>
|
||||||
|
</div>
|
||||||
|
<p id="preview-loader-text" style="color: var(--text-muted); font-size: 0.85rem;">Extracting and upscaling frame. Please wait...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 3: Processing Progress -->
|
||||||
|
<div id="step-progress" class="step-container">
|
||||||
|
<div class="glass-card progress-card">
|
||||||
|
<div class="progress-header">
|
||||||
|
<div>
|
||||||
|
<h2 style="font-family: var(--font-heading); margin-bottom: 0.5rem;">Upscaling Video Pipeline</h2>
|
||||||
|
<p style="color: var(--text-muted); font-size: 0.9rem;">Job ID: <span id="job-id-display" style="font-family: monospace; color: var(--text-main);">-</span></p>
|
||||||
|
</div>
|
||||||
|
<span id="status-badge" class="status-badge queued">Queued</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-weight: bold; margin-bottom: 0.5rem; font-size: 0.95rem;">
|
||||||
|
<span>Progress</span>
|
||||||
|
<span id="progress-percent" style="color: var(--primary);">0%</span>
|
||||||
|
</div>
|
||||||
|
<div class="bar-container">
|
||||||
|
<div id="progress-bar-fill" class="bar-fill"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Statistics grid -->
|
||||||
|
<div class="progress-stats">
|
||||||
|
<div class="stat-card">
|
||||||
|
<span>Status</span>
|
||||||
|
<span id="stat-status">Queued</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<span>Processed Frames</span>
|
||||||
|
<span id="stat-frames">0 / 0</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<span>Estimated ETA</span>
|
||||||
|
<span id="stat-eta" style="color: var(--primary);">Calculating...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Realtime Logs Panel -->
|
||||||
|
<div>
|
||||||
|
<h4 style="font-family: var(--font-heading); margin-bottom: 0.5rem; color: var(--text-muted); font-size: 0.9rem;">Pipeline Live Activity Logs</h4>
|
||||||
|
<div id="log-terminal" class="log-panel">
|
||||||
|
<div class="log-line">Waiting for worker thread...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action control -->
|
||||||
|
<div style="display: flex; justify-content: flex-end;">
|
||||||
|
<button type="button" id="cancel-upscale-btn" class="btn btn-danger">
|
||||||
|
<i class="fa-solid fa-circle-stop"></i> Abort Upscaling Job
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 4: Finished/Completed -->
|
||||||
|
<div id="step-finished" class="step-container">
|
||||||
|
<div class="glass-card finished-card">
|
||||||
|
<div class="success-icon">
|
||||||
|
<i class="fa-solid fa-circle-check"></i>
|
||||||
|
</div>
|
||||||
|
<h2>Video Enhancement Completed!</h2>
|
||||||
|
<p style="color: var(--text-muted); max-width: 500px; margin: 0 auto;">
|
||||||
|
Your video was successfully upscaled and optimized using the Real-ESRGAN Vulkan backend model. Original audio streams have been merged.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<a id="download-link" href="#" class="btn">
|
||||||
|
<i class="fa-solid fa-download"></i> Download Upscaled Video
|
||||||
|
</a>
|
||||||
|
<button type="button" id="finished-play-comparison-btn" class="btn btn-secondary">
|
||||||
|
<i class="fa-solid fa-film"></i> Compare Playback
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="window.location.reload()">
|
||||||
|
<i class="fa-solid fa-rotate-left"></i> Process Another File
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Jobs & Queue Section -->
|
||||||
|
<section class="queue-section glass-card" style="margin-top: 3rem;">
|
||||||
|
<div class="queue-header" style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-color); padding-bottom: 0.75rem; margin-bottom: 1rem;">
|
||||||
|
<h2><i class="fa-solid fa-list-check"></i> Active Jobs & Pipeline Queue</h2>
|
||||||
|
<button type="button" id="refresh-queue-btn" class="btn btn-secondary btn-small">
|
||||||
|
<i class="fa-solid fa-arrows-rotate"></i> Refresh Queue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p style="color: var(--text-muted); font-size: 0.9rem; margin-bottom: 1.5rem;">Monitor running and queued jobs in the queue pipeline. Preview jobs are marked as well.</p>
|
||||||
|
|
||||||
|
<div id="queue-list" class="queue-list" style="display: flex; flex-direction: column; gap: 0.75rem;">
|
||||||
|
<!-- Dynamically populated queue items -->
|
||||||
|
<div class="queue-empty-state" style="text-align: center; padding: 2rem; border: 1px dashed var(--border-color); border-radius: 12px; color: var(--text-muted);">
|
||||||
|
<i class="fa-solid fa-list-ul" style="font-size: 2rem; margin-bottom: 0.5rem; color: var(--primary);"></i>
|
||||||
|
<p>No active or queued jobs at the moment.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Completed Video Gallery -->
|
||||||
|
<section class="gallery-section glass-card" style="margin-top: 3rem;">
|
||||||
|
<div class="gallery-header">
|
||||||
|
<h2><i class="fa-solid fa-photo-film"></i> Completed Enhancements</h2>
|
||||||
|
<div style="display: flex; gap: 0.5rem;">
|
||||||
|
<button type="button" id="refresh-gallery-btn" class="btn btn-secondary btn-small">
|
||||||
|
<i class="fa-solid fa-arrows-rotate"></i> Refresh
|
||||||
|
</button>
|
||||||
|
<button type="button" id="purge-all-btn" class="btn btn-danger btn-small">
|
||||||
|
<i class="fa-solid fa-trash-can-arrow-up"></i> Purge All Data
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="color: var(--text-muted); font-size: 0.9rem; margin-bottom: 1.5rem;">Access all your upscaled videos, play them side-by-side with original sources, or manage the storage.</p>
|
||||||
|
|
||||||
|
<div id="gallery-grid" class="gallery-grid">
|
||||||
|
<!-- Dynamically populated completed items -->
|
||||||
|
<div class="gallery-empty-state">
|
||||||
|
<i class="fa-solid fa-photo-film"></i>
|
||||||
|
<p>No completed jobs found. Start upscaling a video above to see it in the gallery!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Synced Video Playback Modal -->
|
||||||
|
<div id="video-modal" class="modal-overlay" style="display: none;">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3><i class="fa-solid fa-film"></i> Side-by-Side Synced Video Comparison</h3>
|
||||||
|
<button type="button" class="close-btn" id="modal-close-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="video-compare-wrapper">
|
||||||
|
<div class="video-container">
|
||||||
|
<h4>Original (Before)</h4>
|
||||||
|
<video id="modal-video-original" playsinline></video>
|
||||||
|
</div>
|
||||||
|
<div class="video-container">
|
||||||
|
<h4>Upscaled (AI Enhanced)</h4>
|
||||||
|
<video id="modal-video-upscaled" playsinline></video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Synced Controls -->
|
||||||
|
<div class="video-sync-controls">
|
||||||
|
<button type="button" id="modal-play-btn" class="btn btn-circular">
|
||||||
|
<i class="fa-solid fa-play"></i>
|
||||||
|
</button>
|
||||||
|
<div class="time-scrubber-container">
|
||||||
|
<span id="modal-time-current">00:00</span>
|
||||||
|
<input type="range" id="modal-time-slider" min="0" max="100" value="0" step="0.01">
|
||||||
|
<span id="modal-time-duration">00:00</span>
|
||||||
|
</div>
|
||||||
|
<button type="button" id="modal-mute-btn" class="btn btn-secondary btn-circular">
|
||||||
|
<i class="fa-solid fa-volume-high"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Javascript Orchestration -->
|
||||||
|
<script src="app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+1499
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
stop.py — Stop the AI Video Upscaler web application.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python stop.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# ── Paths ────────────────────────────────────────────────────────────────────
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PID_FILE = os.path.join(BASE_DIR, ".uvicorn.pid")
|
||||||
|
|
||||||
|
|
||||||
|
def _is_running(pid: int) -> bool:
|
||||||
|
"""Check whether a process with the given PID is still alive and is our server."""
|
||||||
|
try:
|
||||||
|
os.kill(pid, 0)
|
||||||
|
except OSError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check /proc/{pid}/cmdline to verify it's our server process
|
||||||
|
try:
|
||||||
|
cmdline_path = f"/proc/{pid}/cmdline"
|
||||||
|
if os.path.exists(cmdline_path):
|
||||||
|
with open(cmdline_path, "r") as f:
|
||||||
|
cmdline = f.read()
|
||||||
|
# Check for indicators of our server
|
||||||
|
return "uvicorn" in cmdline or "app.main" in cmdline
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# If we can't read cmdline, default to True (safer fallback)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def stop() -> None:
|
||||||
|
if not os.path.isfile(PID_FILE):
|
||||||
|
print("ℹ No server appears to be running (no pidfile found).")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = int(open(PID_FILE).read().strip())
|
||||||
|
except (ValueError, OSError):
|
||||||
|
print("⚠ Pidfile exists but is unreadable. Removing it.")
|
||||||
|
os.remove(PID_FILE)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _is_running(pid):
|
||||||
|
print(f"ℹ Server (PID {pid}) is not running. Cleaning up pidfile.")
|
||||||
|
os.remove(PID_FILE)
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"🛑 Stopping server (PID {pid}) …")
|
||||||
|
|
||||||
|
# Graceful shutdown (SIGTERM)
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
|
||||||
|
# Wait up to 10 seconds for graceful exit
|
||||||
|
for i in range(20):
|
||||||
|
time.sleep(0.5)
|
||||||
|
if not _is_running(pid):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Force kill if still alive
|
||||||
|
print(f" ⚠ Server didn't stop gracefully, sending SIGKILL …")
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# Clean up pidfile
|
||||||
|
if os.path.isfile(PID_FILE):
|
||||||
|
os.remove(PID_FILE)
|
||||||
|
|
||||||
|
if _is_running(pid):
|
||||||
|
print(f"❌ Failed to stop server (PID {pid}).")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print(f"✅ Server stopped.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
stop()
|
||||||
Reference in New Issue
Block a user