use huge uploader
All checks were successful
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
Jake Walker 2025-01-10 13:48:35 +00:00
parent 8f4f50ec89
commit a412932888
2 changed files with 65 additions and 38 deletions

56
main.py
View file

@ -3,11 +3,11 @@ from datetime import timedelta
from pathlib import Path
from typing import Annotated, Optional
import aiofiles
import podgen
import structlog
from fastapi import FastAPI, Form, HTTPException, Request, Response
from fastapi.responses import FileResponse, RedirectResponse
from fastapi import FastAPI, Form, HTTPException, Request, Response, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
import data
@ -17,6 +17,9 @@ from settings import settings
log = structlog.get_logger()
app = FastAPI()
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
templates = Jinja2Templates(directory="templates")
audio_processor = AudioProcessor()
@ -71,22 +74,48 @@ def finish_processing(
@app.post("/admin/{feed_id}/upload")
async def admin_upload_episode(request: Request, feed_id: str):
async def admin_upload_episode(request: Request, feed_id: str, file: UploadFile):
file_id = request.headers.get("uploader-file-id")
chunks_total = int(request.headers.get("uploader-chunks-total"))
chunk_number = int(request.headers.get("uploader-chunk-number"))
episode_name = request.headers.get("name")
if file_id is None or episode_name is None:
raise HTTPException(400, "Invalid request")
file_id = "".join(c for c in file_id if c.isalnum()).strip()
repo = data.load_repository()
if feed_id not in repo.podcasts:
raise HTTPException(status_code=404, detail="Podcast not found")
try:
filename = Path(urllib.parse.unquote(request.headers["filename"]))
episode = data.Episode(name=filename.stem, file_size=0, file_hash="")
is_last = (chunk_number + 1) == chunks_total
file_name = f"{file_id}_{chunk_number}"
settings.uploads_directory.mkdir(parents=True, exist_ok=True)
with open(settings.uploads_directory / file_name, "wb") as buf:
buf.write(await file.read())
if is_last:
episode = data.Episode(
name=Path(urllib.parse.unquote(episode_name)).stem,
file_size=0,
file_hash="",
)
upload_path = settings.uploads_directory / episode.id
async with aiofiles.open(upload_path, "wb") as f:
async for chunk in request.stream():
await f.write(chunk)
with open(upload_path, "wb") as buf:
chunk = 0
while chunk < chunks_total:
chunk_path = settings.uploads_directory / f"{file_id}_{chunk}"
with open(chunk_path, "rb") as infile:
buf.write(infile.read())
infile.close()
chunk_path.unlink()
chunk += 1
audio_processor.add_file(
upload_path,
@ -95,11 +124,10 @@ async def admin_upload_episode(request: Request, feed_id: str):
feed_id, episode, duration, file_hash, file_size
),
)
except Exception as e:
log.error("Failed to upload file", error=e)
raise HTTPException(status_code=500, detail="Something went wrong")
return {"ok": True}
return JSONResponse({"message": "File Uploaded"}, status_code=200)
return JSONResponse({"message": "Chunk Uploaded"}, status_code=200)
@app.get("/admin/{feed_id}/{episode_id}/delete")

View file

@ -46,12 +46,14 @@
</tbody>
</table>
<script>
<script type="module">
import HugeUploader from "https://cdn.skypack.dev/huge-uploader";
const resp = document.getElementById("response");
const fileInput = document.getElementById("fileInput");
const submitButton = document.getElementById("submitButton");
function reset() {
window.reset = () => {
resp.innerHTML = "";
}
@ -60,7 +62,7 @@
fileInput.disabled = !enabled;
}
function go() {
window.go = () => {
const file = fileInput.files[0];
if (!file) {
return
@ -68,34 +70,31 @@
setFormEnabled(false);
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
const percentComplete = Math.round((event.loaded / event.total) * 100);
resp.innerHTML = "Uploading: " + percentComplete + "%";
const uploader = new HugeUploader({
endpoint: "/admin/{{ id }}/upload",
file: file,
headers: {
"name": encodeURI(file.name)
}
});
xhr.addEventListener("load", () => {
if (xhr.status === 200) {
resp.innerHTML = "Upload complete, check back in a few minutes after the episode has processed.";
uploader.on("error", (err) => {
console.error("Upload error", err);
resp.innerHTML = "Something has gone wrong!";
setFormEnabled(true);
});
uploader.on("progress", (progress) => {
if (progress.detail == 100) return;
resp.innerHTML = `Uploading ${progress.detail}%...`;
});
uploader.on("finish", (body) => {
console.log("Upload complete", body);
resp.innerHTML = "Upload complete! The episode will be processed in the background. This may take a few minutes but it's safe to navigate away.";
setFormEnabled(true);
fileInput.value = "";
} else {
resp.innerHTML = "Upload failed";
}
setFormEnabled(true);
});
xhr.addEventListener("error", () => {
resp.innerHTML = "Upload failed";
setFormEnabled(true);
});
xhr.open("POST", "/admin/{{ id }}/upload", true);
xhr.setRequestHeader("Content-Type", file.type);
xhr.setRequestHeader("filename", encodeURI(file.name));
xhr.send(file);
}
</script>
{% endblock %}