Ashishdubey1974 commited on
Commit
d8240b4
·
1 Parent(s): e414d7b

Deploy deepfake voice detection app

Browse files
Files changed (9) hide show
  1. .gitignore +26 -0
  2. App.js +812 -0
  3. Dockerfile +25 -0
  4. api.py +114 -0
  5. app.py +226 -0
  6. batch_processor.py +106 -0
  7. docker-compose.yml +30 -0
  8. requirements.txt +11 -0
  9. setup.py +22 -0
.gitignore ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.pyo
4
+ *.pyd
5
+ *.swp
6
+ *.log
7
+
8
+ myenv/
9
+ env/
10
+ venv/
11
+
12
+
13
+ .vscode/
14
+
15
+
16
+ .gradio/
17
+
18
+
19
+ .DS_Store
20
+ Thumbs.db
21
+
22
+ .env
23
+
24
+ dist/
25
+ build/
26
+ *.spec
App.js ADDED
@@ -0,0 +1,812 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import {
3
+ Container, Box, Button, Typography, CircularProgress,
4
+ Paper, Grid, Card, CardContent, LinearProgress,
5
+ FormControl, IconButton, Alert, Snackbar, useMediaQuery
6
+ } from '@mui/material';
7
+ import { createTheme, ThemeProvider, styled, alpha } from '@mui/material/styles';
8
+ import MicIcon from '@mui/icons-material/Mic';
9
+ import StopIcon from '@mui/icons-material/Stop';
10
+ import UploadFileIcon from '@mui/icons-material/UploadFile';
11
+ import CloudUploadIcon from '@mui/icons-material/CloudUpload';
12
+ import AudiotrackIcon from '@mui/icons-material/Audiotrack';
13
+ import VolumeUpIcon from '@mui/icons-material/VolumeUp';
14
+ import SecurityIcon from '@mui/icons-material/Security';
15
+ import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
16
+ import { motion } from 'framer-motion';
17
+
18
+ // API endpoint
19
+ const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
20
+
21
+ // Custom theme
22
+ const theme = createTheme({
23
+ palette: {
24
+ primary: {
25
+ main: '#3a86ff',
26
+ light: '#83b9ff',
27
+ dark: '#0056cb',
28
+ },
29
+ secondary: {
30
+ main: '#ff006e',
31
+ light: '#ff5b9e',
32
+ dark: '#c50052',
33
+ },
34
+ success: {
35
+ main: '#38b000',
36
+ light: '#70e000',
37
+ dark: '#008000',
38
+ contrastText: '#ffffff',
39
+ },
40
+ error: {
41
+ main: '#d00000',
42
+ light: '#ff5c4d',
43
+ dark: '#9d0208',
44
+ contrastText: '#ffffff',
45
+ },
46
+ background: {
47
+ default: '#f8f9fa',
48
+ paper: '#ffffff',
49
+ },
50
+ },
51
+ typography: {
52
+ fontFamily: "'Poppins', 'Roboto', 'Helvetica', 'Arial', sans-serif",
53
+ h3: {
54
+ fontWeight: 700,
55
+ letterSpacing: '-0.5px',
56
+ },
57
+ h6: {
58
+ fontWeight: 600,
59
+ },
60
+ subtitle1: {
61
+ fontWeight: 500,
62
+ }
63
+ },
64
+ shape: {
65
+ borderRadius: 12,
66
+ },
67
+ components: {
68
+ MuiButton: {
69
+ styleOverrides: {
70
+ root: {
71
+ textTransform: 'none',
72
+ borderRadius: 8,
73
+ padding: '10px 16px',
74
+ boxShadow: 'none',
75
+ fontWeight: 600,
76
+ },
77
+ containedPrimary: {
78
+ '&:hover': {
79
+ boxShadow: '0 6px 20px rgba(58, 134, 255, 0.3)',
80
+ },
81
+ },
82
+ },
83
+ },
84
+ MuiPaper: {
85
+ styleOverrides: {
86
+ root: {
87
+ boxShadow: '0 8px 40px rgba(0, 0, 0, 0.08)',
88
+ },
89
+ },
90
+ },
91
+ MuiCard: {
92
+ styleOverrides: {
93
+ root: {
94
+ overflow: 'visible',
95
+ },
96
+ },
97
+ },
98
+ },
99
+ });
100
+
101
+ // Styled components
102
+ const VisuallyHiddenInput = styled('input')({
103
+ clip: 'rect(0 0 0 0)',
104
+ clipPath: 'inset(50%)',
105
+ height: 1,
106
+ overflow: 'hidden',
107
+ position: 'absolute',
108
+ bottom: 0,
109
+ left: 0,
110
+ whiteSpace: 'nowrap',
111
+ width: 1,
112
+ });
113
+
114
+ const StyledCard = styled(Card)(({ theme }) => ({
115
+ height: '100%',
116
+ display: 'flex',
117
+ flexDirection: 'column',
118
+ transition: 'transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out',
119
+ '&:hover': {
120
+ transform: 'translateY(-5px)',
121
+ boxShadow: '0 12px 50px rgba(0, 0, 0, 0.1)',
122
+ },
123
+ }));
124
+
125
+ const ResultCard = styled(Card)(({ theme, prediction }) => ({
126
+ backgroundColor: prediction === 'Real'
127
+ ? alpha(theme.palette.success.light, 0.3)
128
+ : prediction === 'Deepfake'
129
+ ? alpha(theme.palette.error.light, 0.3)
130
+ : theme.palette.grey[100],
131
+ borderLeft: `8px solid ${
132
+ prediction === 'Real'
133
+ ? theme.palette.success.main
134
+ : prediction === 'Deepfake'
135
+ ? theme.palette.error.main
136
+ : theme.palette.grey[300]
137
+ }`,
138
+ backdropFilter: 'blur(10px)',
139
+ transition: 'all 0.3s ease',
140
+ }));
141
+
142
+ const GradientHeader = styled(Box)(({ theme }) => ({
143
+ background: `linear-gradient(135deg, ${theme.palette.primary.dark} 0%, ${theme.palette.primary.main} 100%)`,
144
+ color: '#ffffff',
145
+ padding: theme.spacing(6, 2, 8),
146
+ borderRadius: '0 0 24px 24px',
147
+ marginBottom: -theme.spacing(6),
148
+ }));
149
+
150
+ const GlassCard = styled(Card)(({ theme }) => ({
151
+ backgroundColor: alpha(theme.palette.background.paper, 0.8),
152
+ backdropFilter: 'blur(10px)',
153
+ border: `1px solid ${alpha('#fff', 0.2)}`,
154
+ }));
155
+
156
+ const RecordButton = styled(Button)(({ theme, isrecording }) => ({
157
+ borderRadius: '50%',
158
+ minWidth: '64px',
159
+ width: '64px',
160
+ height: '64px',
161
+ padding: 0,
162
+ boxShadow: isrecording === 'true'
163
+ ? `0 0 0 4px ${alpha(theme.palette.error.main, 0.3)}, 0 0 0 8px ${alpha(theme.palette.error.main, 0.15)}`
164
+ : `0 0 0 4px ${alpha(theme.palette.primary.main, 0.3)}, 0 0 0 8px ${alpha(theme.palette.primary.main, 0.15)}`,
165
+ animation: isrecording === 'true' ? 'pulse 1.5s infinite' : 'none',
166
+ '@keyframes pulse': {
167
+ '0%': {
168
+ boxShadow: `0 0 0 0 ${alpha(theme.palette.error.main, 0.7)}`
169
+ },
170
+ '70%': {
171
+ boxShadow: `0 0 0 15px ${alpha(theme.palette.error.main, 0)}`
172
+ },
173
+ '100%': {
174
+ boxShadow: `0 0 0 0 ${alpha(theme.palette.error.main, 0)}`
175
+ }
176
+ }
177
+ }));
178
+
179
+ const AudioWaveAnimation = styled(Box)(({ theme, isplaying }) => ({
180
+ display: 'flex',
181
+ alignItems: 'center',
182
+ justifyContent: 'center',
183
+ gap: '3px',
184
+ height: '40px',
185
+ opacity: isplaying === 'true' ? 1 : 0.3,
186
+ transition: 'opacity 0.3s ease',
187
+ '& .bar': {
188
+ width: '3px',
189
+ backgroundColor: theme.palette.primary.main,
190
+ borderRadius: '3px',
191
+ animation: isplaying === 'true' ? 'soundwave 1s infinite' : 'none',
192
+ },
193
+ '@keyframes soundwave': {
194
+ '0%': { height: '10%' },
195
+ '50%': { height: '100%' },
196
+ '100%': { height: '10%' }
197
+ }
198
+ }));
199
+
200
+ function App() {
201
+ const [file, setFile] = useState(null);
202
+ const [audioUrl, setAudioUrl] = useState(null);
203
+ const [isRecording, setIsRecording] = useState(false);
204
+ const [isPlaying, setIsPlaying] = useState(false);
205
+ const [recorder, setRecorder] = useState(null);
206
+ const [isLoading, setIsLoading] = useState(false);
207
+ const [result, setResult] = useState(null);
208
+ const [error, setError] = useState(null);
209
+ const [modelInfo, setModelInfo] = useState(null);
210
+ const [openSnackbar, setOpenSnackbar] = useState(false);
211
+
212
+ const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
213
+
214
+ // Create audio wave bars for animation
215
+ const audioBars = Array.from({ length: 10 }, (_, i) => {
216
+ const randomHeight = Math.floor(Math.random() * 100) + 1;
217
+ const randomDelay = Math.random();
218
+ return (
219
+ <Box
220
+ key={i}
221
+ className="bar"
222
+ sx={{
223
+ height: `${randomHeight}%`,
224
+ animationDelay: `${randomDelay}s`
225
+ }}
226
+ />
227
+ );
228
+ });
229
+
230
+ // Audio player logic
231
+ const audioRef = React.useRef(null);
232
+
233
+ const handlePlayPause = () => {
234
+ if (audioRef.current) {
235
+ if (isPlaying) {
236
+ audioRef.current.pause();
237
+ } else {
238
+ audioRef.current.play();
239
+ }
240
+ setIsPlaying(!isPlaying);
241
+ }
242
+ };
243
+
244
+ // Fetch model info on component mount
245
+ useEffect(() => {
246
+ fetch(`${API_URL}/model-info/`)
247
+ .then(response => response.json())
248
+ .then(data => setModelInfo(data))
249
+ .catch(err => console.error("Error fetching model info:", err));
250
+ }, []);
251
+
252
+ // Handle audio events
253
+ useEffect(() => {
254
+ const audioElement = audioRef.current;
255
+ if (audioElement) {
256
+ const handleEnded = () => setIsPlaying(false);
257
+ audioElement.addEventListener('ended', handleEnded);
258
+ return () => {
259
+ audioElement.removeEventListener('ended', handleEnded);
260
+ };
261
+ }
262
+ }, [audioUrl]);
263
+
264
+ // Handle file selection
265
+ const handleFileChange = (event) => {
266
+ const selectedFile = event.target.files[0];
267
+ if (selectedFile) {
268
+ setFile(selectedFile);
269
+ setAudioUrl(URL.createObjectURL(selectedFile));
270
+ setIsPlaying(false);
271
+ setResult(null); // Clear previous results
272
+ }
273
+ };
274
+
275
+ // Start audio recording
276
+ const startRecording = async () => {
277
+ try {
278
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
279
+ const mediaRecorder = new MediaRecorder(stream);
280
+ const audioChunks = [];
281
+
282
+ mediaRecorder.addEventListener("dataavailable", event => {
283
+ audioChunks.push(event.data);
284
+ });
285
+
286
+ mediaRecorder.addEventListener("stop", () => {
287
+ const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
288
+ const audioFile = new File([audioBlob], "recorded-audio.wav", { type: 'audio/wav' });
289
+ setFile(audioFile);
290
+ setAudioUrl(URL.createObjectURL(audioBlob));
291
+ setIsPlaying(false);
292
+ setResult(null); // Clear previous results
293
+ });
294
+
295
+ mediaRecorder.start();
296
+ setIsRecording(true);
297
+ setRecorder(mediaRecorder);
298
+ } catch (err) {
299
+ setError("Could not access microphone. Please check permissions.");
300
+ setOpenSnackbar(true);
301
+ console.error("Error accessing microphone:", err);
302
+ }
303
+ };
304
+
305
+ // Stop audio recording
306
+ const stopRecording = () => {
307
+ if (recorder && recorder.state !== "inactive") {
308
+ recorder.stop();
309
+ setIsRecording(false);
310
+ }
311
+ };
312
+
313
+ // Submit audio for analysis
314
+ const handleSubmit = async () => {
315
+ if (!file) {
316
+ setError("Please upload or record an audio file first.");
317
+ setOpenSnackbar(true);
318
+ return;
319
+ }
320
+
321
+ setIsLoading(true);
322
+ setError(null);
323
+
324
+ const formData = new FormData();
325
+ formData.append('file', file);
326
+
327
+ try {
328
+ const response = await fetch(`${API_URL}/detect/`, {
329
+ method: 'POST',
330
+ body: formData,
331
+ });
332
+
333
+ if (!response.ok) {
334
+ throw new Error(`Server responded with status: ${response.status}`);
335
+ }
336
+
337
+ const data = await response.json();
338
+ setResult(data);
339
+ } catch (err) {
340
+ setError(`Error analyzing audio: ${err.message}`);
341
+ setOpenSnackbar(true);
342
+ console.error("Error analyzing audio:", err);
343
+ } finally {
344
+ setIsLoading(false);
345
+ }
346
+ };
347
+
348
+ // Reset everything
349
+ const handleReset = () => {
350
+ setFile(null);
351
+ setAudioUrl(null);
352
+ setResult(null);
353
+ setError(null);
354
+ setIsPlaying(false);
355
+ };
356
+
357
+ // Format chart data
358
+ const getChartData = () => {
359
+ if (!result || !result.probabilities) return [];
360
+
361
+ return Object.entries(result.probabilities).map(([name, value]) => ({
362
+ name,
363
+ value: parseFloat((value * 100).toFixed(2))
364
+ }));
365
+ };
366
+
367
+ // Handle snackbar close
368
+ const handleCloseSnackbar = (event, reason) => {
369
+ if (reason === 'clickaway') {
370
+ return;
371
+ }
372
+ setOpenSnackbar(false);
373
+ };
374
+
375
+ // Animation variants
376
+ const fadeIn = {
377
+ hidden: { opacity: 0, y: 20 },
378
+ visible: { opacity: 1, y: 0, transition: { duration: 0.6 } }
379
+ };
380
+
381
+ return (
382
+ <ThemeProvider theme={theme}>
383
+ <Box sx={{
384
+ backgroundColor: 'background.default',
385
+ minHeight: '100vh',
386
+ paddingBottom: 4
387
+ }}>
388
+ <GradientHeader>
389
+ <Container maxWidth="md">
390
+ <motion.div
391
+ initial={{ opacity: 0, y: -20 }}
392
+ animate={{ opacity: 1, y: 0 }}
393
+ transition={{ duration: 0.7 }}
394
+ >
395
+ <Box sx={{ textAlign: 'center', position: 'relative' }}>
396
+ <Typography variant="h3" component="h1" gutterBottom>
397
+ Deepfake Voice Detector
398
+ </Typography>
399
+
400
+ <Typography variant="subtitle1" sx={{ maxWidth: '700px', mx: 'auto', opacity: 0.9 }}>
401
+ Upload or record audio to instantly verify if it's authentic or AI-generated
402
+ </Typography>
403
+ </Box>
404
+ </motion.div>
405
+ </Container>
406
+ </GradientHeader>
407
+
408
+ <Container maxWidth="md">
409
+ <Box sx={{ mt: 2, mb: 2 }}>
410
+ {modelInfo && (
411
+ <motion.div
412
+ initial={{ opacity: 0 }}
413
+ animate={{ opacity: 1 }}
414
+ transition={{ delay: 0.3, duration: 0.5 }}
415
+ >
416
+ <Box sx={{
417
+ display: 'flex',
418
+ alignItems: 'center',
419
+ justifyContent: 'center',
420
+ gap: 1
421
+ }}>
422
+ <SecurityIcon fontSize="small" sx={{ color: 'text.secondary' }} />
423
+ <Typography variant="body2" color="text.secondary">
424
+ Using model: {modelInfo.model_id} | Accuracy: {(modelInfo.performance.accuracy * 100).toFixed(2)}%
425
+ </Typography>
426
+ </Box>
427
+ </motion.div>
428
+ )}
429
+ </Box>
430
+
431
+ <motion.div
432
+ variants={fadeIn}
433
+ initial="hidden"
434
+ animate="visible"
435
+ >
436
+ <GlassCard elevation={0} sx={{ mb: 4, overflow: 'visible' }}>
437
+ <CardContent sx={{ p: { xs: 2, sm: 3 } }}>
438
+ <Grid container spacing={3}>
439
+ <Grid item xs={12} md={6}>
440
+ <StyledCard variant="outlined">
441
+ <CardContent sx={{
442
+ display: 'flex',
443
+ flexDirection: 'column',
444
+ alignItems: 'center',
445
+ height: '100%',
446
+ p: { xs: 2, sm: 3 }
447
+ }}>
448
+ <Typography variant="h6" component="div" gutterBottom sx={{ mb: 3 }}>
449
+ Upload Audio
450
+ </Typography>
451
+
452
+ <Button
453
+ component="label"
454
+ variant="contained"
455
+ startIcon={<UploadFileIcon />}
456
+ sx={{
457
+ width: '100%',
458
+ py: 1.5,
459
+ mb: 3,
460
+ backgroundColor: theme.palette.primary.light,
461
+ '&:hover': {
462
+ backgroundColor: theme.palette.primary.main,
463
+ }
464
+ }}
465
+ >
466
+ Choose Audio File
467
+ <VisuallyHiddenInput type="file" accept="audio/*" onChange={handleFileChange} />
468
+ </Button>
469
+
470
+ <Typography variant="body2" color="text.secondary" gutterBottom>
471
+ Or record audio directly
472
+ </Typography>
473
+
474
+ <Box sx={{
475
+ display: 'flex',
476
+ flexDirection: 'column',
477
+ alignItems: 'center',
478
+ mt: 2
479
+ }}>
480
+ {!isRecording ? (
481
+ <RecordButton
482
+ variant="contained"
483
+ color="primary"
484
+ onClick={startRecording}
485
+ isrecording="false"
486
+ >
487
+ <MicIcon />
488
+ </RecordButton>
489
+ ) : (
490
+ <RecordButton
491
+ variant="contained"
492
+ color="error"
493
+ onClick={stopRecording}
494
+ isrecording="true"
495
+ >
496
+ <StopIcon />
497
+ </RecordButton>
498
+ )}
499
+ <Typography variant="body2" sx={{ mt: 1, color: isRecording ? 'error.main' : 'text.secondary' }}>
500
+ {isRecording ? 'Recording...' : 'Tap to record'}
501
+ </Typography>
502
+ </Box>
503
+ </CardContent>
504
+ </StyledCard>
505
+ </Grid>
506
+
507
+ <Grid item xs={12} md={6}>
508
+ <StyledCard variant="outlined">
509
+ <CardContent sx={{
510
+ display: 'flex',
511
+ flexDirection: 'column',
512
+ justifyContent: audioUrl ? 'space-between' : 'center',
513
+ height: '100%',
514
+ p: { xs: 2, sm: 3 }
515
+ }}>
516
+ {audioUrl ? (
517
+ <>
518
+ <Box sx={{ textAlign: 'center' }}>
519
+ <Typography variant="h6" component="div" gutterBottom>
520
+ <AudiotrackIcon sx={{ verticalAlign: 'middle', mr: 1 }} />
521
+ Audio Preview
522
+ </Typography>
523
+ </Box>
524
+
525
+ <Box sx={{
526
+ my: 2,
527
+ display: 'flex',
528
+ flexDirection: 'column',
529
+ alignItems: 'center'
530
+ }}>
531
+ <audio
532
+ ref={audioRef}
533
+ src={audioUrl}
534
+ style={{ display: 'none' }}
535
+ onPlay={() => setIsPlaying(true)}
536
+ onPause={() => setIsPlaying(false)}
537
+ />
538
+
539
+ <AudioWaveAnimation isplaying={isPlaying ? 'true' : 'false'}>
540
+ {audioBars}
541
+ </AudioWaveAnimation>
542
+
543
+ <Box sx={{ mt: 2 }}>
544
+ <IconButton
545
+ color="primary"
546
+ onClick={handlePlayPause}
547
+ size="large"
548
+ sx={{
549
+ backgroundColor: alpha(theme.palette.primary.main, 0.1),
550
+ '&:hover': {
551
+ backgroundColor: alpha(theme.palette.primary.main, 0.2),
552
+ }
553
+ }}
554
+ >
555
+ <VolumeUpIcon />
556
+ </IconButton>
557
+ </Box>
558
+
559
+ <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
560
+ {file ? file.name : "Audio loaded"}
561
+ </Typography>
562
+ </Box>
563
+ </>
564
+ ) : (
565
+ <Box sx={{
566
+ p: 3,
567
+ textAlign: 'center',
568
+ display: 'flex',
569
+ flexDirection: 'column',
570
+ alignItems: 'center',
571
+ justifyContent: 'center',
572
+ height: '100%'
573
+ }}>
574
+ <CloudUploadIcon sx={{
575
+ fontSize: 60,
576
+ color: alpha(theme.palette.text.secondary, 0.5),
577
+ mb: 2
578
+ }} />
579
+ <Typography variant="body1" color="text.secondary">
580
+ No audio selected
581
+ </Typography>
582
+ <Typography variant="body2" color="text.secondary" sx={{ mt: 1, opacity: 0.7 }}>
583
+ Upload or record to analyze
584
+ </Typography>
585
+ </Box>
586
+ )}
587
+ </CardContent>
588
+ </StyledCard>
589
+ </Grid>
590
+ </Grid>
591
+ </CardContent>
592
+
593
+ <Box sx={{
594
+ px: { xs: 2, sm: 3 },
595
+ pb: { xs: 2, sm: 3 },
596
+ textAlign: 'center'
597
+ }}>
598
+ <motion.div
599
+ whileHover={{ scale: 1.03 }}
600
+ whileTap={{ scale: 0.97 }}
601
+ >
602
+ <Button
603
+ variant="contained"
604
+ color="primary"
605
+ size="large"
606
+ disabled={!file || isLoading}
607
+ onClick={handleSubmit}
608
+ sx={{
609
+ px: 4,
610
+ py: 1.2,
611
+ fontSize: '1.1rem',
612
+ fontWeight: 600,
613
+ mx: 1,
614
+ minWidth: { xs: '120px', sm: '160px' }
615
+ }}
616
+ >
617
+ {isLoading ? <CircularProgress size={24} sx={{ mr: 1 }} /> : "Analyze Audio"}
618
+ </Button>
619
+ </motion.div>
620
+
621
+ <Button
622
+ variant="outlined"
623
+ color="secondary"
624
+ size="large"
625
+ onClick={handleReset}
626
+ sx={{
627
+ mx: 1,
628
+ mt: { xs: 1, sm: 0 },
629
+ minWidth: { xs: '120px', sm: '120px' }
630
+ }}
631
+ disabled={isLoading || (!file && !audioUrl)}
632
+ >
633
+ Reset
634
+ </Button>
635
+ </Box>
636
+ </GlassCard>
637
+ </motion.div>
638
+
639
+ {isLoading && (
640
+ <motion.div
641
+ initial={{ opacity: 0 }}
642
+ animate={{ opacity: 1 }}
643
+ transition={{ duration: 0.3 }}
644
+ >
645
+ <Box sx={{ width: '100%', my: 4 }}>
646
+ <Typography variant="body2" color="text.secondary" gutterBottom align="center">
647
+ Analyzing audio...
648
+ </Typography>
649
+ <LinearProgress
650
+ sx={{
651
+ height: 8,
652
+ borderRadius: 4,
653
+ backgroundColor: alpha(theme.palette.primary.main, 0.15)
654
+ }}
655
+ />
656
+ </Box>
657
+ </motion.div>
658
+ )}
659
+
660
+ {result && (
661
+ <motion.div
662
+ initial={{ opacity: 0, y: 30 }}
663
+ animate={{ opacity: 1, y: 0 }}
664
+ transition={{ duration: 0.5 }}
665
+ >
666
+ <Box sx={{ my: 4 }}>
667
+ <ResultCard
668
+ elevation={2}
669
+ prediction={result.prediction}
670
+ sx={{ mb: 3 }}
671
+ >
672
+ <CardContent sx={{ p: { xs: 2, sm: 3 } }}>
673
+ <Box sx={{
674
+ display: 'flex',
675
+ flexDirection: { xs: 'column', sm: 'row' },
676
+ alignItems: { xs: 'flex-start', sm: 'center' },
677
+ justifyContent: 'space-between'
678
+ }}>
679
+ <Box>
680
+ <Typography
681
+ variant="h5"
682
+ component="div"
683
+ gutterBottom
684
+ sx={{
685
+ fontWeight: 700,
686
+ color: result.prediction === 'Real'
687
+ ? 'success.dark'
688
+ : 'error.dark'
689
+ }}
690
+ >
691
+ {result.prediction === 'Real' ? '✓ Authentic Voice' : '⚠ Deepfake Detected'}
692
+ </Typography>
693
+ <Typography variant="body1" sx={{ fontWeight: 500 }}>
694
+ Confidence: {(result.confidence * 100).toFixed(2)}%
695
+ </Typography>
696
+ </Box>
697
+
698
+ <Box
699
+ sx={{
700
+ mt: { xs: 2, sm: 0 },
701
+ display: 'flex',
702
+ alignItems: 'center',
703
+ px: 2,
704
+ py: 1,
705
+ backgroundColor: alpha(
706
+ result.prediction === 'Real'
707
+ ? theme.palette.success.main
708
+ : theme.palette.error.main,
709
+ 0.1
710
+ ),
711
+ borderRadius: 2
712
+ }}
713
+ >
714
+ <Typography variant="body2" sx={{ fontWeight: 600, color: result.prediction === 'Real' ? 'success.dark' : 'error.dark' }}>
715
+ {result.prediction === 'Real' ? 'Human Voice' : 'AI-Generated'}
716
+ </Typography>
717
+ </Box>
718
+ </Box>
719
+ </CardContent>
720
+ </ResultCard>
721
+
722
+ <GlassCard elevation={2} sx={{ p: { xs: 2, sm: 3 } }}>
723
+ <Typography variant="h6" gutterBottom sx={{ fontWeight: 600 }}>
724
+ Probability Distribution
725
+ </Typography>
726
+ <Box sx={{ height: isMobile ? 250 : 300, width: '100%', mt: 2 }}>
727
+ <ResponsiveContainer width="100%" height="100%">
728
+ <BarChart
729
+ data={getChartData()}
730
+ margin={{
731
+ top: 30,
732
+ right: 30,
733
+ left: 20,
734
+ bottom: 10,
735
+ }}
736
+ >
737
+ <CartesianGrid strokeDasharray="3 3" stroke={alpha('#000', 0.1)} />
738
+ <XAxis
739
+ dataKey="name"
740
+ tick={{ fill: theme.palette.text.secondary }}
741
+ axisLine={{ stroke: alpha('#000', 0.15) }}
742
+ />
743
+ <YAxis
744
+ label={{
745
+ value: 'Probability (%)',
746
+ angle: -90,
747
+ position: 'insideLeft',
748
+ style: { fill: theme.palette.text.secondary }
749
+ }}
750
+ tick={{ fill: theme.palette.text.secondary }}
751
+ axisLine={{ stroke: alpha('#000', 0.15) }}
752
+ />
753
+ <Tooltip
754
+ formatter={(value) => [`${value}%`, 'Probability']}
755
+ contentStyle={{
756
+ borderRadius: 8,
757
+ border: 'none',
758
+ boxShadow: '0 4px 20px rgba(0,0,0,0.1)',
759
+ backgroundColor: alpha('#fff', 0.95)
760
+ }}
761
+ />
762
+ <Bar
763
+ dataKey="value"
764
+ fill={(entry) => entry.name === 'Real' ? theme.palette.success.main : theme.palette.error.main}
765
+ radius={[8, 8, 0, 0]}
766
+ label={{
767
+ position: 'top',
768
+ formatter: (value) => `${value}%`,
769
+ fill: theme.palette.text.secondary,
770
+ fontSize: 12,
771
+ fontWeight: 600
772
+ }}
773
+ />
774
+ </BarChart>
775
+ </ResponsiveContainer>
776
+ </Box>
777
+ </GlassCard>
778
+
779
+ <Box sx={{ mt: 3, textAlign: 'center' }}>
780
+ <Typography variant="body2" color="text.secondary">
781
+ Note: This model claims {modelInfo ? (modelInfo.performance.accuracy * 100).toFixed(2) : ''}% accuracy, but results may vary depending on audio quality.
782
+ </Typography>
783
+ </Box>
784
+ </Box>
785
+ </motion.div>
786
+ )}
787
+ </Container>
788
+ </Box>
789
+
790
+ <Snackbar
791
+ open={openSnackbar}
792
+ autoHideDuration={6000}
793
+ onClose={handleCloseSnackbar}
794
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
795
+ >
796
+ <Alert
797
+ onClose={handleCloseSnackbar}
798
+ severity="error"
799
+ sx={{
800
+ width: '100%',
801
+ borderRadius: 2,
802
+ boxShadow: '0 4px 20px rgba(0,0,0,0.15)'
803
+ }}
804
+ >
805
+ {error}
806
+ </Alert>
807
+ </Snackbar>
808
+ </ThemeProvider>
809
+ );
810
+ }
811
+
812
+ export default App;
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM pytorch/pytorch:1.12.1-cuda11.3-cudnn8-runtime
2
+
3
+ WORKDIR /app
4
+
5
+
6
+ RUN apt-get update && apt-get install -y \
7
+ ffmpeg \
8
+ libsndfile1 \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+ RUN pip install --no-cache-dir fastapi uvicorn python-multipart
15
+
16
+ ENV TRANSFORMERS_CACHE=/app/model_cache
17
+ ENV HF_HOME=/app/model_cache
18
+
19
+ COPY app.py api.py ./
20
+
21
+ RUN mkdir -p /app/model_cache
22
+
23
+ CMD ["python", "api.py"]
24
+
25
+ EXPOSE 8000
api.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import uvicorn
4
+ from fastapi import FastAPI, File, UploadFile, HTTPException
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from fastapi.responses import JSONResponse
7
+ import shutil
8
+ from pydantic import BaseModel
9
+ from typing import Optional, Dict, Any, List
10
+
11
+ # Import our detection module
12
+ from app import DeepfakeDetector, convert_audio
13
+
14
+ # Initialize the FastAPI app
15
+ app = FastAPI(
16
+ title="Deepfake Voice Detection API",
17
+ description="API for detecting deepfake audio using the MelodyMachine/Deepfake-audio-detection-V2 model",
18
+ version="0.1.0",
19
+ )
20
+
21
+ # Add CORS middleware
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"], # Allows all origins
25
+ allow_credentials=True,
26
+ allow_methods=["*"], # Allows all methods
27
+ allow_headers=["*"], # Allows all headers
28
+ )
29
+
30
+ # Initialize the detector at startup
31
+ detector = None
32
+
33
+ @app.on_event("startup")
34
+ async def startup_event():
35
+ global detector
36
+ detector = DeepfakeDetector()
37
+ print("Deepfake Detector model loaded and ready to use")
38
+
39
+ class PredictionResponse(BaseModel):
40
+ prediction: str
41
+ confidence: float
42
+ probabilities: Dict[str, float]
43
+
44
+ @app.post("/detect/", response_model=PredictionResponse)
45
+ async def detect_audio(file: UploadFile = File(...)):
46
+ """
47
+ Detect if an audio file contains a deepfake voice
48
+ """
49
+ if not file:
50
+ raise HTTPException(status_code=400, detail="No file provided")
51
+
52
+ # Validate file type
53
+ if not file.filename.lower().endswith(('.wav', '.mp3', '.ogg', '.flac')):
54
+ raise HTTPException(
55
+ status_code=400,
56
+ detail="Invalid file format. Only WAV, MP3, OGG, and FLAC files are supported."
57
+ )
58
+
59
+ try:
60
+ # Create a temporary file
61
+ temp_dir = tempfile.gettempdir()
62
+ temp_path = os.path.join(temp_dir, file.filename)
63
+
64
+ # Save uploaded file to the temp location
65
+ with open(temp_path, "wb") as buffer:
66
+ shutil.copyfileobj(file.file, buffer)
67
+
68
+ # Convert audio to required format
69
+ processed_audio = convert_audio(temp_path)
70
+
71
+ # Detect if it's a deepfake
72
+ result = detector.detect(processed_audio)
73
+
74
+ # Clean up the temporary files
75
+ try:
76
+ os.remove(temp_path)
77
+ os.remove(processed_audio) if processed_audio != temp_path else None
78
+ except:
79
+ pass
80
+
81
+ return result
82
+
83
+ except Exception as e:
84
+ raise HTTPException(status_code=500, detail=f"Error processing audio: {str(e)}")
85
+
86
+ @app.get("/health/")
87
+ async def health_check():
88
+ """
89
+ Check if the API is running and the model is loaded
90
+ """
91
+ if detector is None:
92
+ return JSONResponse(
93
+ status_code=503,
94
+ content={"status": "error", "message": "Model not loaded"}
95
+ )
96
+ return {"status": "ok", "model_loaded": True}
97
+
98
+ @app.get("/model-info/")
99
+ async def model_info():
100
+ """
101
+ Get information about the model being used
102
+ """
103
+ return {
104
+ "model_id": "MelodyMachine/Deepfake-audio-detection-V2",
105
+ "base_model": "facebook/wav2vec2-base",
106
+ "performance": {
107
+ "loss": 0.0141,
108
+ "accuracy": 0.9973
109
+ },
110
+ "description": "Fine-tuned model for binary classification distinguishing between real and deepfake audio"
111
+ }
112
+
113
+ if __name__ == "__main__":
114
+ uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True)
app.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import gradio as gr
4
+ import numpy as np
5
+ import librosa
6
+ import soundfile as sf
7
+ from transformers import Wav2Vec2FeatureExtractor, Wav2Vec2ForSequenceClassification
8
+ from pydub import AudioSegment
9
+ import tempfile
10
+ import matplotlib
11
+ matplotlib.use('Agg')
12
+
13
+ # Constants
14
+ MODEL_ID = "MelodyMachine/Deepfake-audio-detection-V2"
15
+ SAMPLE_RATE = 16000
16
+ MAX_DURATION = 30 # maximum audio duration in seconds
17
+
18
+ class DeepfakeDetector:
19
+ def __init__(self, model_id=MODEL_ID):
20
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
21
+ print(f"Using device: {self.device}")
22
+
23
+ print(f"Loading model from {model_id}...")
24
+ self.feature_extractor = Wav2Vec2FeatureExtractor.from_pretrained(model_id)
25
+ self.model = Wav2Vec2ForSequenceClassification.from_pretrained(model_id).to(self.device)
26
+ print("Model loaded successfully!")
27
+
28
+ # Labels for classification
29
+ self.id2label = {0: "Real", 1: "Deepfake"}
30
+
31
+ def preprocess_audio(self, audio_path):
32
+ """Process audio file to match model requirements."""
33
+ try:
34
+ # Load audio file
35
+ y, sr = librosa.load(audio_path, sr=SAMPLE_RATE, mono=True)
36
+
37
+ # Trim silence from the beginning and end
38
+ y, _ = librosa.effects.trim(y, top_db=20)
39
+
40
+ # If audio is longer than MAX_DURATION seconds, take the first MAX_DURATION seconds
41
+ if len(y) > MAX_DURATION * SAMPLE_RATE:
42
+ y = y[:MAX_DURATION * SAMPLE_RATE]
43
+
44
+ return y
45
+ except Exception as e:
46
+ raise ValueError(f"Error preprocessing audio: {str(e)}")
47
+
48
+ def detect(self, audio_path):
49
+ """Detect if audio is real or deepfake."""
50
+ try:
51
+ # Preprocess audio
52
+ audio_array = self.preprocess_audio(audio_path)
53
+
54
+ # Extract features
55
+ inputs = self.feature_extractor(
56
+ audio_array,
57
+ sampling_rate=SAMPLE_RATE,
58
+ return_tensors="pt",
59
+ padding=True
60
+ ).to(self.device)
61
+
62
+ # Get prediction
63
+ with torch.no_grad():
64
+ outputs = self.model(**inputs)
65
+ logits = outputs.logits
66
+ predictions = torch.softmax(logits, dim=1)
67
+
68
+ # Get results
69
+ predicted_class = torch.argmax(predictions, dim=1).item()
70
+ confidence = predictions[0][predicted_class].item()
71
+
72
+ result = {
73
+ "prediction": self.id2label[predicted_class],
74
+ "confidence": float(confidence),
75
+ "probabilities": {
76
+ "Real": float(predictions[0][0].item()),
77
+ "Deepfake": float(predictions[0][1].item())
78
+ }
79
+ }
80
+
81
+ return result
82
+ except Exception as e:
83
+ raise ValueError(f"Error during detection: {str(e)}")
84
+
85
+ def convert_audio(input_file):
86
+ """Convert the audio file to the required format."""
87
+ # Create temp file with .wav extension
88
+ temp_dir = tempfile.gettempdir()
89
+ temp_path = os.path.join(temp_dir, "temp_audio_file.wav")
90
+
91
+ # Handle various input formats
92
+ if input_file.endswith('.mp3'):
93
+ audio = AudioSegment.from_mp3(input_file)
94
+ audio = audio.set_channels(1) # Convert to mono
95
+ audio = audio.set_frame_rate(SAMPLE_RATE) # Set sample rate
96
+ audio.export(temp_path, format="wav")
97
+ elif input_file.endswith('.wav'):
98
+ audio = AudioSegment.from_wav(input_file)
99
+ audio = audio.set_channels(1) # Convert to mono
100
+ audio = audio.set_frame_rate(SAMPLE_RATE) # Set sample rate
101
+ audio.export(temp_path, format="wav")
102
+ elif input_file.endswith('.ogg'):
103
+ audio = AudioSegment.from_ogg(input_file)
104
+ audio = audio.set_channels(1) # Convert to mono
105
+ audio = audio.set_frame_rate(SAMPLE_RATE) # Set sample rate
106
+ audio.export(temp_path, format="wav")
107
+ elif input_file.endswith('.flac'):
108
+ audio = AudioSegment.from_file(input_file, format="flac")
109
+ audio = audio.set_channels(1) # Convert to mono
110
+ audio = audio.set_frame_rate(SAMPLE_RATE) # Set sample rate
111
+ audio.export(temp_path, format="wav")
112
+ else:
113
+ # Try to convert using pydub's generic from_file
114
+ try:
115
+ audio = AudioSegment.from_file(input_file)
116
+ audio = audio.set_channels(1) # Convert to mono
117
+ audio = audio.set_frame_rate(SAMPLE_RATE) # Set sample rate
118
+ audio.export(temp_path, format="wav")
119
+ except:
120
+ raise ValueError(f"Unsupported audio format for file: {input_file}")
121
+
122
+ return temp_path
123
+
124
+ def detect_deepfake(audio_file, detector):
125
+ """Process audio and detect if it's a deepfake."""
126
+ if audio_file is None:
127
+ return {
128
+ "error": "Please upload an audio file."
129
+ }
130
+
131
+ try:
132
+ # Convert audio to required format
133
+ processed_audio = convert_audio(audio_file)
134
+
135
+ # Detect deepfake
136
+ result = detector.detect(processed_audio)
137
+
138
+ # Create a visually appealing output
139
+ prediction = result["prediction"]
140
+ confidence = result["confidence"] * 100
141
+
142
+ # Prepare visualization data
143
+ labels = list(result["probabilities"].keys())
144
+ values = list(result["probabilities"].values())
145
+
146
+ output = {
147
+ "prediction": prediction,
148
+ "confidence": f"{confidence:.2f}%",
149
+ "chart_labels": labels,
150
+ "chart_values": values
151
+ }
152
+
153
+ # Create result text with confidence
154
+ result_text = f"Prediction: {prediction} (Confidence: {confidence:.2f}%)"
155
+
156
+ return result_text, output
157
+ except Exception as e:
158
+ return f"Error: {str(e)}", None
159
+
160
+ def create_interface():
161
+ """Create Gradio interface for the application."""
162
+ # Initialize the deepfake detector
163
+ detector = DeepfakeDetector()
164
+
165
+ with gr.Blocks(title="Deepfake Voice Detector") as interface:
166
+ gr.Markdown("""
167
+ # Deepfake Voice Detector
168
+
169
+ Upload an audio file to check if it's a real human voice or an AI-generated deepfake.
170
+
171
+ **Model:** MelodyMachine/Deepfake-audio-detection-V2 (Accuracy: 99.73%)
172
+ """)
173
+
174
+ with gr.Row():
175
+ with gr.Column(scale=1):
176
+ audio_input = gr.Audio(
177
+ type="filepath",
178
+ label="Upload Audio File",
179
+ sources=["upload", "microphone"]
180
+ )
181
+ submit_btn = gr.Button("Analyze Audio", variant="primary")
182
+
183
+ with gr.Column(scale=1):
184
+ result_text = gr.Textbox(label="Result")
185
+
186
+ # Visualization component
187
+ with gr.Accordion("Detailed Analysis", open=False):
188
+ gr.Markdown("### Confidence Scores")
189
+ confidence_plot = gr.Plot(label="Confidence Scores")
190
+
191
+ # Process function for the submit button
192
+ def process_and_visualize(audio_file):
193
+ result_text, output = detect_deepfake(audio_file, detector)
194
+
195
+ if output:
196
+ # Create bar chart visualization
197
+ import matplotlib.pyplot as plt
198
+
199
+ fig, ax = plt.subplots(figsize=(6, 4))
200
+ bars = ax.bar(output["chart_labels"], output["chart_values"], color=['green', 'red'])
201
+
202
+ # Add percentage labels on top of each bar
203
+ for bar in bars:
204
+ height = bar.get_height()
205
+ ax.text(bar.get_x() + bar.get_width()/2., height + 0.02,
206
+ f'{height*100:.1f}%', ha='center', va='bottom')
207
+
208
+ ax.set_ylim(0, 1.1)
209
+ ax.set_title('Confidence Scores')
210
+ ax.set_ylabel('Probability')
211
+
212
+ return result_text, fig
213
+ else:
214
+ return result_text, None
215
+
216
+ submit_btn.click(
217
+ process_and_visualize,
218
+ inputs=[audio_input],
219
+ outputs=[result_text, confidence_plot]
220
+ )
221
+
222
+ return interface
223
+
224
+ if __name__ == "__main__":
225
+ interface = create_interface()
226
+ interface.launch()
batch_processor.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ import json
4
+ import pandas as pd
5
+ from tqdm import tqdm
6
+ from concurrent.futures import ProcessPoolExecutor, as_completed
7
+ from app import DeepfakeDetector, convert_audio
8
+
9
+ def process_single_file(file_path, detector):
10
+ """Process a single audio file and return the detection result."""
11
+ try:
12
+ # Convert audio to the required format
13
+ processed_audio = convert_audio(file_path)
14
+
15
+ # Detect if it's a deepfake
16
+ result = detector.detect(processed_audio)
17
+
18
+ # Add the file path to the result
19
+ result["file_path"] = file_path
20
+ result["file_name"] = os.path.basename(file_path)
21
+
22
+ # Clean up temporary files if needed
23
+ if processed_audio != file_path:
24
+ try:
25
+ os.remove(processed_audio)
26
+ except:
27
+ pass
28
+
29
+ return result
30
+ except Exception as e:
31
+ return {
32
+ "file_path": file_path,
33
+ "file_name": os.path.basename(file_path),
34
+ "error": str(e)
35
+ }
36
+
37
+ def process_directory(directory_path, output_format='json', max_workers=None, recursive=False):
38
+ """Process all audio files in a directory."""
39
+ # Initialize the detector
40
+ detector = DeepfakeDetector()
41
+
42
+ # Find all audio files
43
+ audio_extensions = ('.wav', '.mp3', '.ogg', '.flac')
44
+ audio_files = []
45
+
46
+ if recursive:
47
+ for root, _, files in os.walk(directory_path):
48
+ for file in files:
49
+ if file.lower().endswith(audio_extensions):
50
+ audio_files.append(os.path.join(root, file))
51
+ else:
52
+ audio_files = [os.path.join(directory_path, f) for f in os.listdir(directory_path)
53
+ if f.lower().endswith(audio_extensions)]
54
+
55
+ if not audio_files:
56
+ print(f"No audio files found in {directory_path}")
57
+ return
58
+
59
+ print(f"Found {len(audio_files)} audio files to process")
60
+
61
+ # Process files with a progress bar
62
+ results = []
63
+
64
+ # Use parallel processing for faster analysis
65
+ with ProcessPoolExecutor(max_workers=max_workers) as executor:
66
+ futures = {executor.submit(process_single_file, file, detector): file for file in audio_files}
67
+
68
+ for future in tqdm(as_completed(futures), total=len(audio_files), desc="Processing audio files"):
69
+ result = future.result()
70
+ results.append(result)
71
+
72
+ # Save results based on output format
73
+ if output_format == 'json':
74
+ output_file = os.path.join(directory_path, "deepfake_detection_results.json")
75
+ with open(output_file, 'w') as f:
76
+ json.dump(results, f, indent=2)
77
+ print(f"Results saved to {output_file}")
78
+
79
+ elif output_format == 'csv':
80
+ output_file = os.path.join(directory_path, "deepfake_detection_results.csv")
81
+ df = pd.DataFrame(results)
82
+ df.to_csv(output_file, index=False)
83
+ print(f"Results saved to {output_file}")
84
+
85
+ # Print summary
86
+ total = len(results)
87
+ real_count = sum(1 for r in results if 'prediction' in r and r['prediction'] == 'Real')
88
+ fake_count = sum(1 for r in results if 'prediction' in r and r['prediction'] == 'Deepfake')
89
+ error_count = sum(1 for r in results if 'error' in r)
90
+
91
+ print("\nSummary:")
92
+ print(f"Total files processed: {total}")
93
+ print(f"Detected as real: {real_count} ({real_count/total*100:.1f}%)")
94
+ print(f"Detected as deepfake: {fake_count} ({fake_count/total*100:.1f}%)")
95
+ print(f"Errors during processing: {error_count} ({error_count/total*100:.1f}%)")
96
+
97
+ if __name__ == "__main__":
98
+ parser = argparse.ArgumentParser(description='Batch process audio files for deepfake detection')
99
+ parser.add_argument('directory', help='Directory containing audio files to process')
100
+ parser.add_argument('--format', choices=['json', 'csv'], default='json', help='Output format (default: json)')
101
+ parser.add_argument('--workers', type=int, default=None, help='Number of worker processes (default: CPU count)')
102
+ parser.add_argument('--recursive', action='store_true', help='Search for audio files recursively in subdirectories')
103
+
104
+ args = parser.parse_args()
105
+
106
+ process_directory(args.directory, args.format, args.workers, args.recursive)
docker-compose.yml ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3'
2
+
3
+ services:
4
+ api:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ ports:
9
+ - "8000:8000"
10
+ command: python api.py
11
+ environment:
12
+ - MODEL_ID=MelodyMachine/Deepfake-audio-detection-V2
13
+ volumes:
14
+ - ./model_cache:/app/model_cache
15
+ deploy:
16
+ resources:
17
+ reservations:
18
+ devices:
19
+ - driver: nvidia
20
+ count: 1
21
+ capabilities: [gpu]
22
+
23
+ web:
24
+ build:
25
+ context: ./frontend
26
+ dockerfile: Dockerfile
27
+ ports:
28
+ - "80:80"
29
+ depends_on:
30
+ - api
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch>=1.10.0
2
+ transformers>=4.16.0
3
+ librosa>=0.8.0
4
+ soundfile>=0.10.3
5
+ gradio>=3.0.0
6
+ matplotlib>=3.4.0
7
+ numpy>=1.20.0
8
+ pydub>=0.25.1
9
+ fastapi>=0.68.0
10
+ uvicorn>=0.15.0
11
+ python-multipart
setup.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="deepfake_voice_detector",
5
+ version="0.1.0",
6
+ packages=find_packages(),
7
+ install_requires=[
8
+ "torch>=1.10.0",
9
+ "transformers>=4.16.0",
10
+ "librosa>=0.8.0",
11
+ "soundfile>=0.10.3",
12
+ "gradio>=3.0.0",
13
+ "matplotlib>=3.4.0",
14
+ "numpy>=1.20.0",
15
+ "pydub>=0.25.1",
16
+ ],
17
+ author="DeepfakeDetector",
18
+ author_email="[email protected]",
19
+ description="An application for detecting deepfake audio using the MelodyMachine/Deepfake-audio-detection-V2 model",
20
+ keywords="deepfake, audio, detection, ai",
21
+ python_requires=">=3.7",
22
+ )