diff --git a/api/backend/settings.py b/api/backend/settings.py index b2e1751..0ce5725 100644 --- a/api/backend/settings.py +++ b/api/backend/settings.py @@ -25,7 +25,7 @@ SECRET_KEY = 'django-insecure-qkh9wpte((ik5#r(l+mj@5rkiy!2-4^&b%0q@7yehgc+i0t20d # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['*'] # Application definition @@ -38,10 +38,12 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', - 'api' + 'api', + "corsheaders", ] MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -100,6 +102,11 @@ AUTH_PASSWORD_VALIDATORS = [ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] +CORS_ALLOW_ALL_ORIGINS = True +CORS_ALLOWED_ORIGINS = [ + "http://localhost:5173", +] + # Internationalization diff --git a/client/package.json b/client/package.json index 96871d2..09bbd81 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,7 @@ "@fontsource/roboto": "^5.0.8", "@mui/icons-material": "^5.15.10", "@mui/material": "^5.15.10", + "axios": "^1.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.1" @@ -29,4 +30,4 @@ "eslint-plugin-react-refresh": "^0.4.5", "vite": "^5.1.0" } -} \ No newline at end of file +} diff --git a/client/src/Pages/ProcessingPage.tsx b/client/src/Pages/ProcessingPage.tsx index 0c9c346..a4e6c1c 100644 --- a/client/src/Pages/ProcessingPage.tsx +++ b/client/src/Pages/ProcessingPage.tsx @@ -1,72 +1,236 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - Typography, - Container, - Paper, - Box, - LinearProgress -} from '@mui/material'; -import StepperComponent from '../components/StepperComponent'; -import { useMediaContext } from '../contexts/MediaContext'; +import React, { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { Snackbar, Alert, Button } from "@mui/material"; +import { + Typography, + Container, + Paper, + Box, + LinearProgress, +} from "@mui/material"; +import StepperComponent from "../components/StepperComponent"; +import { useMediaContext } from "../contexts/MediaContext"; +import axios from "axios"; function ProcessingPage() { const navigate = useNavigate(); - const { mediaFile } = useMediaContext(); + const { mediaFile, response } = useMediaContext(); const [progress, setProgress] = useState(0); - - useEffect(() => { - if (!mediaFile) { - navigate('/upload'); + const [open, setOpen] = useState(false); + const [message, setMessage] = useState(""); + const [severity, setSeverity] = useState< + "success" | "error" | "warning" | "info" + >("info"); + const showToast = ( + msg: string, + type: "success" | "error" | "warning" | "info" + ) => { + setMessage(msg); + setSeverity(type); + setOpen(true); + }; + const processNormalize = async () => { + try { + const formData = new FormData(); + formData.append("file_uuid", response.file_uuid); + const res = await axios.post("http://127.0.0.1:8000/api/normalize", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + console.log("response from server:", res); + if (res.status === 200 && res.data) { + showToast(res.data.message, "success"); + processTrim(); + } else { + showToast("Audio Normalization failed", "error"); + } + } catch (error) { + console.error("Normalization failed:", error); + } + }; + const processTrim = async () => { + try { + const formData = new FormData(); + formData.append("file_uuid", response.file_uuid); + const res = await axios.post( + "http://127.0.0.1:8000/api/trim", + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + console.log("response from server:", res); + if (res.status === 200 && res.data) { + showToast(res.data.message, "success"); + if (response.audio_class === "Music") { + processResampling(); + }else{ + processNoiseReduce() + } + } else { + showToast("Audio Trimming failed", "error"); + } + } catch (error) { + console.error("Trimming failed:", error); return; } + }; + const processResampling = async () => { + try { + const formData = new FormData(); + formData.append("file_uuid", response.file_uuid); + formData.append("sr", String(response.sr)); + const res = await axios.post( + "http://127.0.0.1:8000/api/resample", + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + console.log("response from server:", res); + if (res.status === 200 && res.data) { + showToast(res.data.message, "success"); + processSeparate() + + } else { + showToast("Audio Resampling failed", "error"); + } + } catch (error) { + console.error("Resampling failed:", error); + } + }; + + const processNoiseReduce = async () => { + try { + const formData = new FormData(); + formData.append("file_uuid", response.file_uuid); + const res = await axios.post( + "http://127.0.0.1:8000/api/noisereduce", + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + console.log("response from server:", res); + if (res.status === 200 && res.data) { + showToast(res.data.message, "success"); + } else { + showToast("Audio NoiseRemoval failed", "error"); + } + } catch (error) { + console.error("NoiseRemoval failed:", error); + } + }; + const processSeparate = async () => { + try { + const formData = new FormData(); + formData.append("file_uuid", response.file_uuid); + const res = await axios.post( + "http://127.0.0.1:8000/api/separate", + formData, + { + headers: { + "Content-Type": "multipart/form-data", + }, + } + ); + console.log("response from server:", res); + if (res.status === 200 && res.data) { + showToast("Audio separated successfully", "success"); + } else { + showToast("Audio separation failed", "error"); + } + } catch (error) { + console.error("Separation failed:", error); + } + }; + + + + useEffect(() => { + if (!mediaFile) { + navigate("/upload"); + return; + } + console.log("Normalizing...."); + processNormalize(); + + // Simulate processing progress const interval = setInterval(() => { setProgress((prevProgress) => { const newProgress = prevProgress + 10; if (newProgress >= 100) { clearInterval(interval); - setTimeout(() => navigate('/results'), 500); + setTimeout(() => navigate("/results"), 500); return 100; } return newProgress; }); }, 800); - + return () => clearInterval(interval); }, [mediaFile, navigate]); - + return ( - - + + Processing Your Media Please wait while we process your file - + - + {Math.round(progress)}% Complete - + - {progress < 30 ? "Analyzing media..." : - progress < 60 ? "Applying processing..." : - progress < 90 ? "Finalizing..." : - "Almost done..."} + {progress < 30 + ? "Analyzing media..." + : progress < 60 + ? "Applying processing..." + : progress < 90 + ? "Finalizing..." + : "Almost done..."} + setOpen(false)} + anchorOrigin={{ vertical: "top", horizontal: "right" }} + > + setOpen(false)} + severity={severity} + sx={{ width: "100%" }} + > + {message} + + ); } -export default ProcessingPage; \ No newline at end of file +export default ProcessingPage; diff --git a/client/src/Pages/ResultsPage.tsx b/client/src/Pages/ResultsPage.tsx index c1cf4ad..650d2dd 100644 --- a/client/src/Pages/ResultsPage.tsx +++ b/client/src/Pages/ResultsPage.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; +import axios from 'axios'; import { Typography, Container, @@ -22,9 +23,45 @@ import { useMediaContext } from '../contexts/MediaContext'; function ResultsPage() { const navigate = useNavigate(); const theme = useTheme(); - const { mediaFile, clearMedia } = useMediaContext(); + const { mediaFile, clearMedia,response } = useMediaContext(); const [isPlaying, setIsPlaying] = useState(false); const videoRef = useRef(null); + + const processDownload = async() => { + try { + console.log('download started..') + const res = await axios.get("http://127.0.0.1:8000/api/download", { + params: { file_uuid: response.file_uuid }, // Attach file_uuid as a query param + responseType: "blob", // Treat response as a file + }); + + if (res.status === 200) { + const blob = new Blob([res.data]); + const url = window.URL.createObjectURL(blob); + + // Extract filename from headers or use default + const contentDisposition = res.headers["content-disposition"]; + const filename = contentDisposition + ? contentDisposition.split("filename=")[1] + : "downloaded_file"; + + // Auto-download the file (no user interaction needed) + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); + + // Cleanup + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } else { + console.error("Download failed, server responded with:", res); + } + } catch (error) { + console.error("Download error:", error); + } + }; useEffect(() => { if (!mediaFile) { @@ -152,7 +189,7 @@ function ResultsPage() { color="primary" fullWidth sx={{ mb: 1 }} - onClick={() => alert('Downloading file...')} + onClick={() => processDownload()} > Download diff --git a/client/src/Pages/UploadPage.tsx b/client/src/Pages/UploadPage.tsx index ffccebe..af77901 100644 --- a/client/src/Pages/UploadPage.tsx +++ b/client/src/Pages/UploadPage.tsx @@ -1,29 +1,31 @@ -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { - Typography, - Container, - Button, - Paper, - Box, - useTheme -} from '@mui/material'; -import { +import React, { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import { + Typography, + Container, + Button, + Paper, + Box, + useTheme, +} from "@mui/material"; +import { CloudUpload as CloudUploadIcon, VolumeUp as VolumeUpIcon, - Movie as MovieIcon -} from '@mui/icons-material'; -import StepperComponent from '../components/StepperComponent'; -import { useMediaContext } from '../contexts/MediaContext'; + Movie as MovieIcon, +} from "@mui/icons-material"; +import StepperComponent from "../components/StepperComponent"; +import { useMediaContext } from "../contexts/MediaContext"; function UploadPage() { const navigate = useNavigate(); const theme = useTheme(); - const { setMediaFile } = useMediaContext(); // ✅ Correct function name + const { setMediaFile, setResponse, response } = useMediaContext(); // ✅ Correct function name const [file, setFile] = useState(null); const [isDragging, setIsDragging] = useState(false); - const [fileError, setFileError] = useState(''); - + const [fileError, setFileError] = useState(""); + const [upload, setUpload] = useState(false); + const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); @@ -44,16 +46,17 @@ function UploadPage() { const handleFileChange = (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0] || null; validateAndSetFile(selectedFile); + handleUpload(selectedFile); }; const validateAndSetFile = (file: File | null) => { - setFileError(''); + setFileError(""); if (!file) return; const fileType = file.type; - if (!fileType.includes('audio') && !fileType.includes('video')) { - setFileError('Please upload only audio or video files.'); + if (!fileType.includes("audio") && !fileType.includes("video")) { + setFileError("Please upload only audio or video files."); return; } @@ -62,22 +65,61 @@ function UploadPage() { const handleContinue = () => { if (file) { + setMediaFile({ name: file.name, url: URL.createObjectURL(file), - type: file.type + type: file.type, }); // ✅ Corrected function call - navigate('/preview'); + navigate("/preview"); } else { - setFileError('Please upload a file to continue.'); + setFileError("Please upload a file to continue."); } }; + const handleUpload = async (file: File | null) => { + if (!file) { + console.error("No file selected!"); + return; // Exit early if file is null + } + + const formData = new FormData(); + formData.append("file", file); // ✅ No more errors because we checked `file` is not null. + + try { + const res = await axios.post<{ + file_uuid: string; + sr: number; + audio_class: string; + }>("http://127.0.0.1:8000/api/upload", formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + + console.log("Upload response:", res); + + if (res.status === 201 && res.data) { + setResponse( ({ + audio_class: res.data.audio_class, + file_uuid: res.data.file_uuid, + sr: res.data.sr, + })); + setUpload(true); + } + } catch (error) { + console.error("Upload failed:", error); + } + }; + + useEffect(() => { + console.log("Updated response:", response); + }, [response]); return ( - + Upload Your Media @@ -87,35 +129,43 @@ function UploadPage() { document.getElementById('fileInput')?.click()} + onClick={() => document.getElementById("fileInput")?.click()} > - {file ? file.name : 'Drop your file here or click to browse'} + {file ? file.name : "Drop your file here or click to browse"} {file && ( - {file.type.includes('video') ? : } + {file.type.includes("video") ? ( + + ) : ( + + )} {file.type} - {(file.size / (1024 * 1024)).toFixed(2)} MB )} @@ -127,11 +177,20 @@ function UploadPage() { )} - - - diff --git a/client/src/contexts/MediaContext.tsx b/client/src/contexts/MediaContext.tsx index a4fbfdc..7ad877d 100644 --- a/client/src/contexts/MediaContext.tsx +++ b/client/src/contexts/MediaContext.tsx @@ -3,15 +3,24 @@ import React, { createContext, useState, useContext } from 'react'; interface MediaContextType { mediaFile: { name: string; url: string; type: string } | null; setMediaFile: (file: { name: string; url: string; type: string }) => void; + response: { file_uuid: string; sr: number; audio_class: string }; + setResponse: (response: { file_uuid: string; sr: number; audio_class: string }) => void; } + const MediaContext = createContext(undefined); export const MediaProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { const [mediaFile, setMediaFile] = useState(null); + const [response, setResponse] = useState({ + audio_class: "", + file_uuid: "", + sr: 0, + }); + return ( - + {children} ); diff --git a/client/src/main.tsx b/client/src/main.tsx index b0a66b6..28e7b4a 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import '@fontsource/roboto/300.css'; @@ -7,7 +6,5 @@ import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; ReactDOM.createRoot(document.getElementById('root')).render( - - - , + ); \ No newline at end of file