Merge pull request #51 from joelmathewthomas/feature/record

Feature/record
This commit is contained in:
Joel Mathew Thomas
2025-03-21 17:00:02 +05:30
committed by GitHub
2 changed files with 270 additions and 110 deletions
+172 -76
View File
@@ -1,25 +1,31 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { import {
Typography, Typography,
Container, Container,
Button, Button,
Paper, Paper,
Box, Box,
Card, Card,
CardContent, CardContent,
Grid, Grid,
useTheme useTheme,
Slide,
Zoom,
} from '@mui/material'; } from '@mui/material';
import { import {
CloudUpload as CloudUploadIcon, CloudUpload as CloudUploadIcon,
PlayArrow as PlayArrowIcon, PlayArrow as PlayArrowIcon,
Check as CheckIcon Check as CheckIcon,
GraphicEq as GraphicEqIcon,
NoiseAware as NoiseAwareIcon,
MusicNote as MusicNoteIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
function LandingPage() { function LandingPage() {
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme(); const theme = useTheme();
const showContent = true;
return ( return (
<Box <Box
sx={{ sx={{
@@ -33,73 +39,163 @@ function LandingPage() {
}} }}
> >
<Container maxWidth="md"> <Container maxWidth="md">
<Paper <Slide direction="down" in={showContent} mountOnEnter unmountOnExit>
elevation={3} <Paper
sx={{ elevation={3}
padding: theme.spacing(6), sx={{
textAlign: 'center', padding: theme.spacing(6),
background: 'rgba(255, 255, 255, 0.9)', textAlign: 'center',
}} background: 'rgba(255, 255, 255, 0.9)',
> }}
<Typography variant="h3" gutterBottom color="primary"> >
Welcome to FreqSplit <Typography variant="h3" gutterBottom color="primary">
</Typography> Welcome to FreqSplit
<Typography variant="h5" paragraph color="textSecondary"> </Typography>
Upload, preview, and process your audio files with ease <Typography variant="h5" paragraph color="textSecondary">
</Typography> Upload, preview, and process your audio files with ease
<Box sx={{ mt: 4 }}> </Typography>
<Button <Box sx={{ mt: 4 }}>
variant="contained" <Button
color="primary" variant="contained"
size="large" color="primary"
startIcon={<CloudUploadIcon />} size="large"
onClick={() => navigate('/upload')} startIcon={<CloudUploadIcon />}
sx={{ mr: 2 }} onClick={() => navigate('/upload')}
> sx={{ mr: 2 }}
Get Started >
</Button> Get Started
</Box> </Button>
</Paper> </Box>
</Paper>
<Grid container spacing={4} sx={{ mt: 4 }}> </Slide>
<Grid container spacing={4} sx={{ mt: 4, justifyContent: 'center' }}>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<Card sx={{ height: '100%' }}> <Zoom in={showContent} style={{ transitionDelay: '100ms' }}>
<CardContent sx={{ textAlign: 'center' }}> <Card
<CloudUploadIcon color="primary" sx={{ fontSize: 60, mb: 2 }} /> sx={{
<Typography variant="h5" gutterBottom> height: '100%',
Easy Upload transition: 'transform 0.3s ease-in-out',
</Typography> '&:hover': { transform: 'scale(1.05)' },
<Typography variant="body1" color="textSecondary"> }}
Drag and drop your media files for quick processing >
</Typography> <CardContent sx={{ textAlign: 'center' }}>
</CardContent> <CloudUploadIcon color="primary" sx={{ fontSize: 60, mb: 2 }} />
</Card> <Typography variant="h5" gutterBottom>
Upload or Record
</Typography>
<Typography variant="body1" color="textSecondary">
Drag and drop your media files or record directly for quick processing.
</Typography>
</CardContent>
</Card>
</Zoom>
</Grid> </Grid>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<Card sx={{ height: '100%' }}> <Zoom in={showContent} style={{ transitionDelay: '200ms' }}>
<CardContent sx={{ textAlign: 'center' }}> <Card
<PlayArrowIcon color="primary" sx={{ fontSize: 60, mb: 2 }} /> sx={{
<Typography variant="h5" gutterBottom> height: '100%',
Preview Media transition: 'transform 0.3s ease-in-out',
</Typography> '&:hover': { transform: 'scale(1.05)' },
<Typography variant="body1" color="textSecondary"> }}
Review your files before processing >
</Typography> <CardContent sx={{ textAlign: 'center' }}>
</CardContent> <PlayArrowIcon color="primary" sx={{ fontSize: 60, mb: 2 }} />
</Card> <Typography variant="h5" gutterBottom>
Preview Media
</Typography>
<Typography variant="body1" color="textSecondary">
Review your files before processing.
</Typography>
</CardContent>
</Card>
</Zoom>
</Grid> </Grid>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<Card sx={{ height: '100%' }}> <Zoom in={showContent} style={{ transitionDelay: '300ms' }}>
<CardContent sx={{ textAlign: 'center' }}> <Card
<CheckIcon color="primary" sx={{ fontSize: 60, mb: 2 }} /> sx={{
<Typography variant="h5" gutterBottom> height: '100%',
View Results transition: 'transform 0.3s ease-in-out',
</Typography> '&:hover': { transform: 'scale(1.05)' },
<Typography variant="body1" color="textSecondary"> }}
Download and share your processed media >
</Typography> <CardContent sx={{ textAlign: 'center' }}>
</CardContent> <CheckIcon color="primary" sx={{ fontSize: 60, mb: 2 }} />
</Card> <Typography variant="h5" gutterBottom>
View Results
</Typography>
<Typography variant="body1" color="textSecondary">
Download and share your processed media.
</Typography>
</CardContent>
</Card>
</Zoom>
</Grid>
<Grid item xs={12} md={4}>
<Zoom in={showContent} style={{ transitionDelay: '400ms' }}>
<Card
sx={{
height: '100%',
transition: 'transform 0.3s ease-in-out',
'&:hover': { transform: 'scale(1.05)' },
}}
>
<CardContent sx={{ textAlign: 'center' }}>
<GraphicEqIcon color="primary" sx={{ fontSize: 60, mb: 2 }} />
<Typography variant="h5" gutterBottom>
Audio Source Separation (Demucs)
</Typography>
<Typography variant="body1" color="textSecondary">
Separate audio sources with state-of-the-art Demucs.
</Typography>
</CardContent>
</Card>
</Zoom>
</Grid>
<Grid item xs={12} md={4}>
<Zoom in={showContent} style={{ transitionDelay: '500ms' }}>
<Card
sx={{
height: '100%',
transition: 'transform 0.3s ease-in-out',
'&:hover': { transform: 'scale(1.05)' },
}}
>
<CardContent sx={{ textAlign: 'center' }}>
<NoiseAwareIcon color="primary" sx={{ fontSize: 60, mb: 2 }} />
<Typography variant="h5" gutterBottom>
Noise Reduction (DeepFilterNet)
</Typography>
<Typography variant="body1" color="textSecondary">
Reduce noise effectively using DeepFilterNet.
</Typography>
</CardContent>
</Card>
</Zoom>
</Grid>
<Grid item xs={12} md={4}>
<Zoom in={showContent} style={{ transitionDelay: '600ms' }}>
<Card
sx={{
height: '100%',
transition: 'transform 0.3s ease-in-out',
'&:hover': { transform: 'scale(1.05)' },
}}
>
<CardContent sx={{ textAlign: 'center' }}>
<MusicNoteIcon color="primary" sx={{ fontSize: 60, mb: 2 }} />
<Typography variant="h5" gutterBottom>
Audio Processing (Librosa)
</Typography>
<Typography variant="body1" color="textSecondary">
Advanced audio processing and manipulation with Librosa.
</Typography>
</CardContent>
</Card>
</Zoom>
</Grid> </Grid>
</Grid> </Grid>
</Container> </Container>
+98 -34
View File
@@ -13,7 +13,8 @@ import {
} from "@mui/material"; } from "@mui/material";
import { import {
CloudUpload as CloudUploadIcon, CloudUpload as CloudUploadIcon,
VolumeUp as VolumeUpIcon, Mic as MicIcon,
Stop as StopIcon
} from "@mui/icons-material"; } from "@mui/icons-material";
import StepperComponent from "../components/StepperComponent"; import StepperComponent from "../components/StepperComponent";
import { useWebSocket } from "../contexts/WebSocketContext"; import { useWebSocket } from "../contexts/WebSocketContext";
@@ -29,6 +30,46 @@ function UploadPage() {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [upload, setUpload] = useState(false); const [upload, setUpload] = useState(false);
const [inputEnabled, setInputEnabled] = useState(false); const [inputEnabled, setInputEnabled] = useState(false);
const [isRecording, setIsRecording] = useState(false);
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);
const startRecording = async () => {
setUpload(false);
setResponse({
audio_class: "",
file_uuid: "",
sr: 0,
spectrogram: "",
spec_sr: 0
});
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream);
setMediaRecorder(recorder);
const chunks: Blob[] = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: "audio/wav" });
const file = new File([blob], "recording.wav", { type: "audio/wav" });
validateAndSetFile(file);
handleUpload(file);
};
recorder.start();
setIsRecording(true);
} catch (error) {
console.error("Failed to start recording:", error);
showErrorToast("Failed to start recording");
}
};
const stopRecording = () => {
if (mediaRecorder) {
mediaRecorder.stop();
setIsRecording(false);
}
};
const showErrorToast = (message: string) => { const showErrorToast = (message: string) => {
setToastMessage(message); setToastMessage(message);
@@ -220,8 +261,52 @@ function UploadPage() {
<Paper elevation={3} sx={{ p: 4, mt: 4, textAlign: "center" }}> <Paper elevation={3} sx={{ p: 4, mt: 4, textAlign: "center" }}>
<Typography variant="h4" gutterBottom color="primary"> <Typography variant="h4" gutterBottom color="primary">
Upload Your Media Upload or Record Your Media
</Typography> </Typography>
<Box
sx={{
border: `2px dashed ${
isDragging ? theme.palette.primary.main : theme.palette.divider
}`,
borderRadius: 2,
p: 6,
mt: 3,
mb: 3,
backgroundColor: isDragging
? "rgba(63, 81, 181, 0.08)"
: "transparent",
transition: "all 0.3s ease",
cursor: "pointer",
overflow: "auto",
}}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => document.getElementById("fileInput")?.click()}
>
<input
type="file"
id="fileInput"
style={{ display: "none" }}
onChange={handleFileChange}
accept="audio/*"
disabled={!inputEnabled}
/>
<CloudUploadIcon color="primary" sx={{ fontSize: 64, mb: 2 }} />
<Typography
variant="h6"
gutterBottom
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
maxWidth: "100%",
}}
>
{file ? file.name : "Drop your file here or click to browse files"}
</Typography>
<Typography>Supported formats: MP3, WAV, AAC, OGG</Typography>
</Box>
<Box <Box
sx={{ sx={{
@@ -244,39 +329,18 @@ function UploadPage() {
onDrop={handleDrop} onDrop={handleDrop}
onClick={() => document.getElementById("fileInput")?.click()} onClick={() => document.getElementById("fileInput")?.click()}
> >
<input <Button
type="file" variant="contained"
id="fileInput" color={isRecording ? "secondary" : "primary"}
style={{ display: "none" }} startIcon={isRecording ? <StopIcon /> : <MicIcon />}
onChange={handleFileChange} onClick={(e) => {
accept="audio/*" e.stopPropagation(); // Prevent click from reaching file input
disabled={!inputEnabled} isRecording ? stopRecording() : startRecording();
/> }}
>
<CloudUploadIcon color="primary" sx={{ fontSize: 64, mb: 2 }} /> {isRecording ? "Stop Recording" : "Start Recording"}
<Typography </Button>
variant="h6"
gutterBottom
sx={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
maxWidth: "100%", // Ensure it adapts to the box size
}}
>
{file ? file.name : "Drop your file here or click to browse files"}
</Typography>
<Typography>
Max file size: 100MB
</Typography>
{file && (
<Typography variant="body2" color="textSecondary">
{file.type.includes("audio") ? <VolumeUpIcon sx={{ mr: 1 }} /> : null}
{file.type} - {(file.size / (1024 * 1024)).toFixed(2)} MB
</Typography>
)}
</Box> </Box>
<Box sx={{ mt: 4, display: "flex", justifyContent: "space-between" }}> <Box sx={{ mt: 4, display: "flex", justifyContent: "space-between" }}>
<Button <Button
variant="outlined" variant="outlined"