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 import FastAPI, UploadFile, File, Form, HTTPException
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
import yaml
|
import yaml
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -9,14 +10,21 @@ from utils import parse_yaml_config, get_presets_dir
|
|||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
# Allow CORS (frontend access)
|
# CORS setup for frontend use
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=["*"], # For dev; restrict for production
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
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/")
|
@app.post("/upload-config/")
|
||||||
async def upload_config(file: UploadFile = File(...)):
|
async def upload_config(file: UploadFile = File(...)):
|
||||||
config_data = yaml.safe_load(await file.read())
|
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):
|
async def transcode(yaml_config: dict):
|
||||||
try:
|
try:
|
||||||
ffmpeg_cmd = parse_yaml_config(yaml_config)
|
ffmpeg_cmd = parse_yaml_config(yaml_config)
|
||||||
subprocess.run(ffmpeg_cmd, check=True)
|
process = subprocess.run(ffmpeg_cmd, check=True, capture_output=True, text=True)
|
||||||
return {"status": "success", "cmd": ffmpeg_cmd}
|
return {
|
||||||
except Exception as e:
|
"status": "success",
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
"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/")
|
@app.get("/browse/")
|
||||||
def browse(path: str = "/data"):
|
def browse(path: str = "/data"):
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise HTTPException(status_code=404, detail="Path not found")
|
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}
|
return {"path": path, "entries": entries}
|
||||||
|
|
||||||
@app.post("/save-preset/")
|
@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