added html frontend
This commit is contained in:
parent
3b5621142c
commit
333cb01229
54
.gitignore
vendored
Normal file
54
.gitignore
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
env/
|
||||
venv/
|
||||
|
||||
# VSCode settings
|
||||
.vscode/
|
||||
|
||||
# macOS and Linux system files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker-related
|
||||
*.log
|
||||
*.pid
|
||||
*.tar
|
||||
*.sock
|
||||
|
||||
# Ignore test output or temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.swp
|
||||
|
||||
# Python artifacts
|
||||
*.egg-info/
|
||||
*.egg
|
||||
dist/
|
||||
build/
|
||||
|
||||
# PyInstaller
|
||||
*.spec
|
||||
|
||||
# Coverage reports
|
||||
htmlcov/
|
||||
.coverage
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
|
||||
# MyPy
|
||||
.mypy_cache/
|
||||
|
||||
# Presets and data you don’t want in Git
|
||||
presets/
|
||||
data/
|
||||
|
||||
# Frontend build output (optional if using React)
|
||||
frontend/build/
|
||||
@ -1,6 +1,7 @@
|
||||
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import yaml
|
||||
import os
|
||||
import subprocess
|
||||
@ -9,14 +10,21 @@ from utils import parse_yaml_config, get_presets_dir
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# Allow CORS (frontend access)
|
||||
# CORS setup for frontend use
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["*"], # For dev; restrict for production
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Serve static files (e.g., HTML GUI)
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return FileResponse("static/index.html")
|
||||
|
||||
@app.post("/upload-config/")
|
||||
async def upload_config(file: UploadFile = File(...)):
|
||||
config_data = yaml.safe_load(await file.read())
|
||||
@ -26,16 +34,24 @@ async def upload_config(file: UploadFile = File(...)):
|
||||
async def transcode(yaml_config: dict):
|
||||
try:
|
||||
ffmpeg_cmd = parse_yaml_config(yaml_config)
|
||||
subprocess.run(ffmpeg_cmd, check=True)
|
||||
return {"status": "success", "cmd": ffmpeg_cmd}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
process = subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
|
||||
return {
|
||||
"status": "success",
|
||||
"cmd": ffmpeg_cmd,
|
||||
"output": process.stdout,
|
||||
"error": process.stderr
|
||||
}
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise HTTPException(status_code=500, detail=e.stderr or str(e))
|
||||
|
||||
@app.get("/browse/")
|
||||
def browse(path: str = "/data"):
|
||||
if not os.path.exists(path):
|
||||
raise HTTPException(status_code=404, detail="Path not found")
|
||||
entries = [{"name": f, "is_dir": os.path.isdir(os.path.join(path, f))} for f in os.listdir(path)]
|
||||
entries = [{
|
||||
"name": f,
|
||||
"is_dir": os.path.isdir(os.path.join(path, f))
|
||||
} for f in os.listdir(path)]
|
||||
return {"path": path, "entries": entries}
|
||||
|
||||
@app.post("/save-preset/")
|
||||
|
||||
120
backend/static/index.html
Normal file
120
backend/static/index.html
Normal file
@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>FFmpeg Transcoder</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background: #1e1e1e;
|
||||
color: #fff;
|
||||
padding: 2rem;
|
||||
}
|
||||
h1 {
|
||||
color: #4fc3f7;
|
||||
}
|
||||
label, input, select, textarea, button {
|
||||
display: block;
|
||||
margin: 1rem 0;
|
||||
width: 100%;
|
||||
}
|
||||
input, select, textarea {
|
||||
padding: 0.5rem;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
border: 1px solid #555;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button {
|
||||
background: #4fc3f7;
|
||||
color: #000;
|
||||
padding: 0.7rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
#log {
|
||||
background: #222;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>FFmpeg Web Transcoder</h1>
|
||||
|
||||
<form id="upload-form">
|
||||
<label for="yamlFile">Upload YAML Config:</label>
|
||||
<input type="file" id="yamlFile" name="yamlFile" accept=".yml, .yaml" required />
|
||||
<button type="submit">Upload & Transcode</button>
|
||||
</form>
|
||||
|
||||
<label for="presetSelect">Or Select a Preset:</label>
|
||||
<select id="presetSelect">
|
||||
<option disabled selected>Loading presets...</option>
|
||||
</select>
|
||||
<button onclick="loadPreset()">Load Preset</button>
|
||||
|
||||
<label for="fileBrowser">Select Input File:</label>
|
||||
<input type="text" id="inputFile" placeholder="/data/input.mp4" />
|
||||
|
||||
<button onclick="startTranscode()">Start Transcoding</button>
|
||||
|
||||
<div id="log"></div>
|
||||
|
||||
<script>
|
||||
const presetSelect = document.getElementById('presetSelect');
|
||||
|
||||
async function fetchPresets() {
|
||||
const res = await fetch('http://localhost:8000/list-presets/');
|
||||
const data = await res.json();
|
||||
presetSelect.innerHTML = data.presets.map(p => `<option value="${p}">${p}</option>`).join('');
|
||||
}
|
||||
|
||||
async function loadPreset() {
|
||||
const preset = presetSelect.value;
|
||||
const res = await fetch(`http://localhost:8000/preset/${preset}`);
|
||||
const text = await res.text();
|
||||
alert(`Loaded Preset: \n\n${text}`);
|
||||
}
|
||||
|
||||
document.getElementById('upload-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const fileInput = document.getElementById('yamlFile');
|
||||
const formData = new FormData();
|
||||
formData.append('file', fileInput.files[0]);
|
||||
|
||||
const res = await fetch('http://localhost:8000/upload-config/', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const result = await res.json();
|
||||
document.getElementById('log').innerText = JSON.stringify(result, null, 2);
|
||||
});
|
||||
|
||||
async function startTranscode() {
|
||||
const inputPath = document.getElementById('inputFile').value;
|
||||
const preset = presetSelect.value;
|
||||
const presetRes = await fetch(`http://localhost:8000/preset/${preset}`);
|
||||
const yamlText = await presetRes.text();
|
||||
const yamlObj = jsyaml.load(yamlText);
|
||||
yamlObj.input_file = inputPath;
|
||||
|
||||
const res = await fetch('http://localhost:8000/transcode/', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(yamlObj)
|
||||
});
|
||||
const result = await res.json();
|
||||
document.getElementById('log').innerText = JSON.stringify(result, null, 2);
|
||||
}
|
||||
|
||||
window.onload = fetchPresets;
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/js-yaml@4.1.0/dist/js-yaml.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user