Backend MP4 conversion (#23)

* ADD conversion queue

* ADD RemuxService for MP4 conversion

* REMOVE unused conversion queue

* REORGANISE Job-related classes

* ADD Job stages

* REVERT to old commit, using Spring Async instead

* ADD asynchronous processing for video tasks

* PATCH and streamline progress tracking

* ADD asynchronous video processing and job restructuring

* REFACTOR job service method

* ADD job remux functionality

* ADD remuxing endpoint

* PATCH complete flag not updating in API response

* ADD progress type in frontend

* ADD reset functionality for job status

* PATCH missing progress bar for subsequent exports

* REDESIGN settings box

* ADD tracking video file conversion in frontend

* PATCH extension bug

* REMOVE autowired decorator
This commit is contained in:
Dylan De Faoite
2025-07-31 20:48:34 +02:00
committed by GitHub
parent 338eb605fd
commit 20f7ec8db4
24 changed files with 324 additions and 185 deletions

View File

@@ -1,25 +1,75 @@
import {useState} from "react";
import {useNavigate} from "react-router-dom";
import { uploadFile } from "../utils/endpoints"
import {uploadFile, convertFile, getProgress} from "../utils/endpoints"
import BlueButton from "../components/buttons/BlueButton.tsx";
import Box from "../components/Box.tsx";
const clipUpload = () => {
const ClipUpload = () => {
const [file, setFile] = useState<File | null>(null);
const navigate = useNavigate();
const [error, setError] = useState<null | string>(null);
const [progress, setProgress] = useState<number>(0);
const isVideoFileSupported = (file: File): boolean => {
const video = document.createElement("video");
if (file.type && video.canPlayType(file.type) !== "") {
return true;
}
const extension = file.name.split(".").pop()?.toLowerCase();
const extensionToMime: Record<string, string> = {
mp4: "video/mp4",
webm: "video/webm",
ogg: "video/ogg",
mov: "video/quicktime",
};
if (extension && extensionToMime[extension]) {
return video.canPlayType(extensionToMime[extension]) !== "";
}
return false;
};
const press = (() => {
if (!file) {
setError("Please choose a file");
return;
}
uploadFile(file)
.then(uuid => navigate(`/create/${uuid}`))
.then(uuid => {
if (isVideoFileSupported(file)) {
navigate(`/create/${uuid}`)
} else {
convertFile(uuid);
const interval = setInterval(async() => await pollProgress(uuid, interval), 500);
}
})
.catch((e: Error) => setError(`Failed to upload file: ${e.message}`));
});
const pollProgress = async (id: string, intervalId: number) => {
getProgress(id)
.then((progress) => {
setProgress(progress.conversion.progress);
if (progress.conversion.complete) {
clearInterval(intervalId);
navigate(`/create/${id}`)
}
})
.catch((err: Error) => {
setError(`Failed to fetch progress: ${err.message}`);
clearInterval(intervalId);
});
}
return (
<Box className={"flex flex-col justify-between gap-3 p-5"}>
<input
@@ -37,8 +87,12 @@ const clipUpload = () => {
</BlueButton>
<label className={"text-center text-red-500"}>{error}</label>
<progress
value={progress}
className={"bg-gray-300 rounded-lg h-1"}>
</progress>
</Box>
)
};
export default clipUpload;
export default ClipUpload;