cobalt-api / app.py
raju014's picture
Upload 4 files
aa027fd verified
"""
Video Upscaler - HuggingFace Spaces (FastAPI + HTML)
Simple, reliable video upscaling using FFmpeg.
No Gradio - cleaner and fewer dependencies.
"""
from fastapi import FastAPI, File, UploadFile, Form
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
import subprocess
import os
import tempfile
import shutil
import uuid
app = FastAPI(title="Video Upscaler 4K")
# Upscale resolutions
RESOLUTIONS = {
"720p": (1280, 720),
"1080p": (1920, 1080),
"1440p": (2560, 1440),
"2160p": (3840, 2160),
}
# Create temp directory for outputs
OUTPUT_DIR = "/tmp/upscaled"
os.makedirs(OUTPUT_DIR, exist_ok=True)
HTML_PAGE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎬 Video Upscaler 4K</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
color: #fff;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
}
h1 {
text-align: center;
margin-bottom: 10px;
font-size: 2em;
}
.subtitle {
text-align: center;
color: #aaa;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}
input[type="file"] {
width: 100%;
padding: 15px;
background: rgba(255,255,255,0.1);
border: 2px dashed #666;
border-radius: 10px;
color: #fff;
cursor: pointer;
}
input[type="file"]:hover {
border-color: #00d9ff;
}
select {
width: 100%;
padding: 12px;
border-radius: 8px;
border: none;
background: rgba(255,255,255,0.2);
color: #fff;
font-size: 16px;
}
select option {
background: #1a1a2e;
color: #fff;
}
button {
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #00d9ff, #0066ff);
border: none;
border-radius: 10px;
color: #fff;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 20px rgba(0,217,255,0.4);
}
button:disabled {
background: #555;
cursor: not-allowed;
transform: none;
}
.status {
margin-top: 20px;
padding: 15px;
border-radius: 10px;
text-align: center;
display: none;
}
.status.show { display: block; }
.status.processing {
background: rgba(255,193,7,0.2);
border: 1px solid #ffc107;
}
.status.success {
background: rgba(40,167,69,0.2);
border: 1px solid #28a745;
}
.status.error {
background: rgba(220,53,69,0.2);
border: 1px solid #dc3545;
}
.download-link {
display: inline-block;
margin-top: 10px;
padding: 10px 20px;
background: #28a745;
color: #fff;
text-decoration: none;
border-radius: 8px;
}
.tips {
margin-top: 30px;
padding: 15px;
background: rgba(255,255,255,0.05);
border-radius: 10px;
font-size: 14px;
color: #aaa;
}
.tips h3 { color: #fff; margin-bottom: 10px; }
.tips li { margin: 5px 0; }
</style>
</head>
<body>
<div class="container">
<h1>🎬 Video Upscaler 4K</h1>
<p class="subtitle">Upscale your videos to Ultra HD quality</p>
<form id="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<label>πŸ“€ Upload Video (MP4, MKV, AVI, MOV)</label>
<input type="file" name="video" id="videoInput" accept="video/*" required>
</div>
<div class="form-group">
<label>🎯 Target Resolution</label>
<select name="resolution" id="resolution">
<option value="720p">720p (HD)</option>
<option value="1080p">1080p (Full HD)</option>
<option value="1440p">1440p (2K)</option>
<option value="2160p" selected>2160p (4K Ultra HD)</option>
</select>
</div>
<div class="form-group">
<label>βš™οΈ Quality</label>
<select name="quality" id="quality">
<option value="fast">Fast (Lower Quality)</option>
<option value="balanced" selected>Balanced</option>
<option value="best">Best Quality (Slow)</option>
</select>
</div>
<button type="submit" id="submitBtn">πŸš€ Upscale Video</button>
</form>
<div id="status" class="status"></div>
<div class="tips">
<h3>πŸ’‘ Tips:</h3>
<ul>
<li>4K (2160p) = 3840x2160 pixels</li>
<li>Processing time: ~2-5 min per minute of video</li>
<li>Best Quality uses CRF 15 (near-lossless)</li>
<li>Max file size: 100MB for free tier</li>
</ul>
</div>
</div>
<script>
const form = document.getElementById('uploadForm');
const statusDiv = document.getElementById('status');
const submitBtn = document.getElementById('submitBtn');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
submitBtn.disabled = true;
submitBtn.textContent = '⏳ Processing...';
statusDiv.className = 'status show processing';
statusDiv.innerHTML = '⏳ Uploading and processing... This may take a few minutes.';
try {
const response = await fetch('/upscale', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
statusDiv.className = 'status show success';
statusDiv.innerHTML = `
βœ… Success! Video upscaled to ${result.resolution}<br>
πŸ“ Size: ${result.size_mb} MB<br>
<a href="${result.download_url}" class="download-link" download>πŸ“₯ Download Video</a>
`;
} else {
statusDiv.className = 'status show error';
statusDiv.innerHTML = '❌ Error: ' + result.error;
}
} catch (error) {
statusDiv.className = 'status show error';
statusDiv.innerHTML = '❌ Error: ' + error.message;
}
submitBtn.disabled = false;
submitBtn.textContent = 'πŸš€ Upscale Video';
});
</script>
</body>
</html>
"""
@app.get("/", response_class=HTMLResponse)
async def home():
return HTML_PAGE
@app.post("/upscale")
async def upscale_video(
video: UploadFile = File(...),
resolution: str = Form("2160p"),
quality: str = Form("balanced")
):
try:
# Get target dimensions
if resolution not in RESOLUTIONS:
resolution = "2160p"
width, height = RESOLUTIONS[resolution]
# Quality settings
quality_map = {
"fast": ("23", "fast"),
"balanced": ("18", "medium"),
"best": ("15", "slow")
}
crf, preset = quality_map.get(quality, ("18", "medium"))
# Save uploaded file
upload_id = str(uuid.uuid4())[:8]
input_path = f"/tmp/input_{upload_id}.mp4"
output_path = f"{OUTPUT_DIR}/upscaled_{upload_id}.mp4"
with open(input_path, "wb") as f:
content = await video.read()
f.write(content)
# FFmpeg command for high-quality upscaling
cmd = [
'ffmpeg', '-y',
'-i', input_path,
'-vf', f'scale={width}:{height}:flags=lanczos',
'-c:v', 'libx264',
'-crf', crf,
'-preset', preset,
'-c:a', 'aac', '-b:a', '192k',
'-movflags', '+faststart',
output_path
]
# Run FFmpeg
process = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
# Clean up input
os.remove(input_path)
if process.returncode == 0 and os.path.exists(output_path):
size_mb = round(os.path.getsize(output_path) / (1024 * 1024), 1)
return {
"success": True,
"resolution": f"{width}x{height}",
"size_mb": size_mb,
"download_url": f"/download/{upload_id}"
}
else:
return {"success": False, "error": "FFmpeg processing failed"}
except subprocess.TimeoutExpired:
return {"success": False, "error": "Processing timeout (>1 hour)"}
except Exception as e:
return {"success": False, "error": str(e)}
@app.get("/download/{upload_id}")
async def download_file(upload_id: str):
file_path = f"{OUTPUT_DIR}/upscaled_{upload_id}.mp4"
if os.path.exists(file_path):
return FileResponse(file_path, filename=f"upscaled_{upload_id}.mp4", media_type="video/mp4")
return {"error": "File not found"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)