Gradi02 commited on
Commit
6b15151
Β·
unverified Β·
2 Parent(s): fc6d61f8e30b6a

Merge pull request #1 from Tobkubos/backend-setup

Browse files
QUICKSTART.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quick Reference Guide
2
+
3
+ ## πŸš€ Starting the Backend
4
+
5
+ ### First Time Setup (Windows)
6
+ ```powershell
7
+ cd backend
8
+ python setup.py
9
+ venv\Scripts\activate
10
+ python main.py
11
+ ```
12
+
13
+ ### First Time Setup (macOS/Linux)
14
+ ```bash
15
+ cd backend
16
+ python setup.py
17
+ source venv/bin/activate
18
+ python main.py
19
+
20
+ or
21
+
22
+ "uvicorn app:app --reload --host 127.0.0.1 --port 8000"
23
+ "uvicorn app:app --host 127.0.0.1 --port 8000"
24
+ ```
25
+
26
+ ### Subsequent Times
27
+ ```bash
28
+ cd backend
29
+ run.bat
30
+ ```
31
+
32
+ ## πŸ“‘ API Quick Test
33
+
34
+ ### Health Check
35
+ ```bash
36
+ curl http://localhost:8000/
37
+ ```
38
+
39
+ ### Analyze a File
40
+ ```bash
41
+ curl -X POST http://localhost:8000/analyze \
42
+ -H "Content-Type: application/json" \
43
+ -d '{"file_url": "https://example.com/video.mp4"}'
44
+ ```
45
+
46
+ ### Documentation
47
+ - **Swagger UI**: http://localhost:8000/docs
48
+ - **ReDoc**: http://localhost:8000/redoc
49
+
README.md CHANGED
@@ -1 +0,0 @@
1
- # DiscordBot
 
 
TODO.md ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ Bezpieczeństwo (SSRF - Server-Side Request Forgery): Twój backend pobiera pliki z dowolnego przekazanego adresu URL. Złośliwy użytkownik mógłby podać URL wskazujący na wewnętrzne zasoby Twojej sieci (np. http://localhost:8080/admin). Warto zaimplementować w download_file walidację, która pozwala na pobieranie plików wyłącznie z zaufanych domen (np. tylko z *.discordapp.com i *.media.discordapp.net).
2
+
backend/.env.example ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deepfake Detection Service - Environment Variables
2
+
3
+ # Application Settings
4
+ APP_NAME=Deepfake Detection Service
5
+ APP_VERSION=1.0.0
6
+ DEBUG=True
7
+ RELOAD=True
8
+
9
+ # Server Configuration
10
+ HOST=127.0.0.1
11
+ PORT=8000
12
+
13
+ # File Handling
14
+ DOWNLOAD_TIMEOUT=30
15
+ MAX_FILE_SIZE=104857600
16
+
17
+ # ML Model Configuration
18
+ DEFAULT_DETECTOR_MODEL=mock
19
+ # Supported models: mock, deepseek, openai, etc.
20
+
21
+ # Redis Configuration (for future queuing)
22
+ REDIS_ENABLED=False
23
+ REDIS_URL=redis://localhost:6379
24
+ REDIS_QUEUE_NAME=deepfake_analysis
25
+
26
+ # Logging
27
+ LOG_LEVEL=INFO
28
+ LOG_FILE=
backend/.gitignore ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environments
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .venv
28
+
29
+ # IDE
30
+ .vscode/
31
+ .idea/
32
+ *.swp
33
+ *.swo
34
+ *~
35
+ .DS_Store
36
+
37
+ # Environment variables
38
+ .env
39
+ .env.local
40
+ .env.*.local
41
+
42
+ # Logs
43
+ logs/
44
+ *.log
45
+ *.log.*
46
+
47
+ # Redis
48
+ dump.rdb
49
+
50
+ # Testing
51
+ .pytest_cache/
52
+ .coverage
53
+ htmlcov/
54
+
55
+ # Temporary files
56
+ *.tmp
57
+ *.temp
backend/README.md ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deepfake Detection Service Backend
2
+
3
+ ### Prerequisites
4
+ - Python 3.8+
5
+ - pip or conda
6
+
7
+ ### Installation
8
+
9
+ 1. **Navigate to the backend directory:**
10
+ ```bash
11
+ cd backend
12
+ ```
13
+
14
+ 2. **Create a virtual environment (recommended):**
15
+ ```bash
16
+ # Using venv
17
+ python -m venv venv
18
+
19
+ # Activate virtual environment
20
+ # On Windows:
21
+ venv\Scripts\activate
22
+ # On macOS/Linux:
23
+ source venv/bin/activate
24
+ ```
25
+
26
+ 3. **Install dependencies:**
27
+ ```bash
28
+ pip install -r requirements.txt
29
+ ```
30
+
31
+ 4. **Run the server:**
32
+ ```bash
33
+ python main.py
34
+ ```
35
+
36
+ The server will start on `http://127.0.0.1:8000`
37
+
38
+ ## πŸ“– API Documentation
39
+
40
+ Once the server is running, interactive API documentation is available at:
41
+ - Swagger UI: `http://127.0.0.1:8000/docs`
42
+ - ReDoc: `http://127.0.0.1:8000/redoc`
43
+
44
+ ## πŸ”Œ API Endpoints
45
+
46
+ ### Health Check
47
+ ```bash
48
+ GET /
49
+ ```
50
+
51
+ Returns service status and available models.
52
+
53
+ **Response:**
54
+ ```json
55
+ {
56
+ "status": "ok",
57
+ "service": "Deepfake Detection Service",
58
+ "version": "1.0.0",
59
+ "available_models": ["mock"]
60
+ }
61
+ ```
62
+
63
+ ### Analyze File
64
+ ```bash
65
+ POST /analyze
66
+ Content-Type: application/json
67
+
68
+ {
69
+ "file_url": "https://example.com/video.mp4",
70
+ "model": "mock"
71
+ }
72
+ ```
73
+
74
+ **Request Parameters:**
75
+ - `file_url` (required): URL of the file to analyze
76
+ - `model` (optional): Detector model to use. Defaults to configured model
77
+
78
+ **Response (200 OK):**
79
+ ```json
80
+ {
81
+ "is_deepfake": true,
82
+ "confidence": 0.847,
83
+ "analysis_time": 1.234,
84
+ "model_used": "mock"
85
+ }
86
+ ```
87
+
88
+ **Error Responses:**
89
+ - `400 Bad Request`: Invalid URL, file too large, or unsupported model
90
+ - `408 Request Timeout`: File download timed out
91
+ - `500 Internal Server Error`: Server error during analysis
92
+
93
+
94
+ ### Using curl:
95
+ ```bash
96
+ curl -X POST http://localhost:8000/analyze \
97
+ -H "Content-Type: application/json" \
98
+ -d '{"file_url": "https://example.com/video.mp4"}'
99
+ ```
100
+
101
+
102
+ The API provides comprehensive error handling:
103
+
104
+ ```python
105
+ # Invalid URL
106
+ {
107
+ "error": "Invalid URL format",
108
+ "status_code": 400,
109
+ "details": null
110
+ }
111
+
112
+ # File too large
113
+ {
114
+ "error": "File size exceeds maximum allowed size of 104857600 bytes",
115
+ "status_code": 400,
116
+ "details": null
117
+ }
118
+
119
+ # Download timeout
120
+ {
121
+ "error": "File download timed out",
122
+ "status_code": 408,
123
+ "details": null
124
+ }
125
+
126
+ # Unsupported model
127
+ {
128
+ "error": "Detector model 'invalid' is not supported. Available models: mock",
129
+ "status_code": 400,
130
+ "details": null
131
+ }
132
+ ```
133
+
backend/app/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.api.routes import router as api_router
3
+ from app.core.logging_config import setup_logging
4
+
5
+ setup_logging()
6
+
7
+ app = FastAPI(
8
+ title="Deepfake Detection Service",
9
+ description="Backend service for deepfake detection with support for multiple ML models",
10
+ version="1.0.0",
11
+ )
12
+
13
+ app.include_router(api_router)
backend/app/api/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """API routes and endpoints."""
backend/app/api/routes.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from fastapi import APIRouter, HTTPException
3
+
4
+ from app.models.schemas import (
5
+ AnalysisRequest,
6
+ AnalysisResponse,
7
+ ErrorResponse,
8
+ HealthResponse,
9
+ TextAnalysisRequest,
10
+ ImageAnalysisRequest,
11
+ VideoAnalysisRequest,
12
+ FileAnalysisRequest,
13
+ )
14
+ from app.services.download import download_file
15
+ from app.services.text_analyzer import analyze_text
16
+ from app.services.image_analyzer import analyze_image
17
+ from app.services.detector import get_detector
18
+ from app.core.config import get_settings
19
+ from app.utils.exceptions import DeepfakeDetectionError
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+ router = APIRouter()
24
+
25
+
26
+ @router.get(
27
+ "/",
28
+ response_model=HealthResponse,
29
+ tags=["Health"],
30
+ summary="Health check endpoint",
31
+ )
32
+ async def health_check() -> HealthResponse:
33
+ settings = get_settings()
34
+ logger.info("Health check endpoint accessed")
35
+
36
+ available_models = ["mock"]
37
+ supported_types = ["text", "image", "video", "file"]
38
+
39
+ return HealthResponse(
40
+ status="ok",
41
+ service="Deepfake Detection Service",
42
+ version=settings.APP_VERSION,
43
+ available_models=available_models,
44
+ supported_types=supported_types,
45
+ )
46
+
47
+
48
+ @router.post(
49
+ "/analyze",
50
+ response_model=AnalysisResponse,
51
+ responses={
52
+ 400: {"model": ErrorResponse, "description": "Bad request"},
53
+ 408: {"model": ErrorResponse, "description": "Request timeout"},
54
+ 500: {"model": ErrorResponse, "description": "Internal server error"},
55
+ },
56
+ tags=["Analysis"],
57
+ summary="Analyze content for deepfake detection",
58
+ )
59
+ async def analyze(request: AnalysisRequest) -> AnalysisResponse:
60
+ settings = get_settings()
61
+ detector_model = None
62
+
63
+ if isinstance(request, TextAnalysisRequest):
64
+ detector_model = request.model or settings.DEFAULT_DETECTOR_MODEL
65
+ logger.info(f"Received text analysis request, length: {len(request.text)} chars, model: {detector_model}")
66
+
67
+ try:
68
+ detector = get_detector(detector_model)
69
+ except ValueError as e:
70
+ logger.error(f"Invalid detector model: {str(e)}")
71
+ raise HTTPException(status_code=400, detail=str(e))
72
+
73
+ text_bytes = request.text.encode('utf-8')
74
+ analysis_result = await detector.detect(text_bytes)
75
+
76
+ logger.info(f"Text analysis completed. Result: {analysis_result}")
77
+
78
+ return AnalysisResponse(
79
+ is_deepfake=analysis_result["is_deepfake"],
80
+ confidence=analysis_result["confidence"],
81
+ analysis_time=analysis_result["analysis_time"],
82
+ model_used=detector_model,
83
+ content_type="text",
84
+ )
85
+
86
+ elif isinstance(request, ImageAnalysisRequest):
87
+ detector_model = request.model or settings.DEFAULT_DETECTOR_MODEL
88
+ logger.info(f"Received image analysis request for URL: {request.image_url}, model: {detector_model}")
89
+
90
+ try:
91
+ detector = get_detector(detector_model)
92
+ except ValueError as e:
93
+ logger.error(f"Invalid detector model: {str(e)}")
94
+ raise HTTPException(status_code=400, detail=str(e))
95
+
96
+ try:
97
+ image_bytes = await download_file(str(request.image_url))
98
+ if not image_bytes:
99
+ raise HTTPException(status_code=500, detail="Failed to download image")
100
+ except DeepfakeDetectionError as e:
101
+ raise HTTPException(status_code=e.status_code, detail=e.message)
102
+
103
+ analysis_result = await detector.detect(image_bytes)
104
+
105
+ logger.info(f"Image analysis completed. Result: {analysis_result}")
106
+
107
+ return AnalysisResponse(
108
+ is_deepfake=analysis_result["is_deepfake"],
109
+ confidence=analysis_result["confidence"],
110
+ analysis_time=analysis_result["analysis_time"],
111
+ model_used=detector_model,
112
+ content_type="image",
113
+ )
114
+
115
+ elif isinstance(request, VideoAnalysisRequest):
116
+ detector_model = request.model or settings.DEFAULT_DETECTOR_MODEL
117
+ logger.info(f"Received video analysis request for URL: {request.video_url}, model: {detector_model}")
118
+
119
+ try:
120
+ detector = get_detector(detector_model)
121
+ except ValueError as e:
122
+ logger.error(f"Invalid detector model: {str(e)}")
123
+ raise HTTPException(status_code=400, detail=str(e))
124
+
125
+ try:
126
+ video_bytes = await download_file(str(request.video_url))
127
+ if not video_bytes:
128
+ raise HTTPException(status_code=500, detail="Failed to download video")
129
+ except DeepfakeDetectionError as e:
130
+ raise HTTPException(status_code=e.status_code, detail=e.message)
131
+
132
+ analysis_result = await detector.detect(video_bytes)
133
+
134
+ logger.info(f"Video analysis completed. Result: {analysis_result}")
135
+
136
+ return AnalysisResponse(
137
+ is_deepfake=analysis_result["is_deepfake"],
138
+ confidence=analysis_result["confidence"],
139
+ analysis_time=analysis_result["analysis_time"],
140
+ model_used=detector_model,
141
+ content_type="video",
142
+ )
143
+
144
+ elif isinstance(request, FileAnalysisRequest):
145
+ detector_model = request.model or settings.DEFAULT_DETECTOR_MODEL
146
+ logger.info(f"Received file analysis request for URL: {request.file_url}, model: {detector_model}")
147
+
148
+ try:
149
+ detector = get_detector(detector_model)
150
+ except ValueError as e:
151
+ logger.error(f"Invalid detector model: {str(e)}")
152
+ raise HTTPException(status_code=400, detail=str(e))
153
+
154
+ try:
155
+ file_bytes = await download_file(str(request.file_url))
156
+ if not file_bytes:
157
+ raise HTTPException(status_code=500, detail="Failed to download file")
158
+ except DeepfakeDetectionError as e:
159
+ raise HTTPException(status_code=e.status_code, detail=e.message)
160
+
161
+ analysis_result = await detector.detect(file_bytes)
162
+
163
+ logger.info(f"File analysis completed. Result: {analysis_result}")
164
+
165
+ return AnalysisResponse(
166
+ is_deepfake=analysis_result["is_deepfake"],
167
+ confidence=analysis_result["confidence"],
168
+ analysis_time=analysis_result["analysis_time"],
169
+ model_used=detector_model,
170
+ content_type="file",
171
+ )
172
+
173
+ else:
174
+ raise HTTPException(status_code=400, detail="Unsupported content type")
backend/app/core/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Core application configuration and setup."""
backend/app/core/config.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import Optional
3
+ from functools import lru_cache
4
+
5
+
6
+ class Settings:
7
+ """Application settings with environment variable support."""
8
+
9
+ # Application
10
+ APP_NAME: str = "Deepfake Detection Service"
11
+ APP_VERSION: str = "1.0.0"
12
+ DEBUG: bool = os.getenv("DEBUG", "True").lower() == "true"
13
+
14
+ # Server
15
+ HOST: str = os.getenv("HOST", "127.0.0.1")
16
+ PORT: int = int(os.getenv("PORT", "8000"))
17
+
18
+ # File handling
19
+ DOWNLOAD_TIMEOUT: int = int(os.getenv("DOWNLOAD_TIMEOUT", "30"))
20
+ MAX_FILE_SIZE: int = int(os.getenv("MAX_FILE_SIZE", str(100 * 1024 * 1024))) # 100 MB
21
+
22
+ # ML Model configuration
23
+ DEFAULT_DETECTOR_MODEL: str = os.getenv("DEFAULT_DETECTOR_MODEL", "mock")
24
+ # Supported models: "mock", "deepseek", "openai", etc. (easy to add more)
25
+
26
+ # Redis configuration (for future queuing)
27
+ REDIS_ENABLED: bool = os.getenv("REDIS_ENABLED", "False").lower() == "true"
28
+ REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379")
29
+ REDIS_QUEUE_NAME: str = os.getenv("REDIS_QUEUE_NAME", "deepfake_analysis")
30
+
31
+ # Logging
32
+ LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
33
+ LOG_FILE: Optional[str] = os.getenv("LOG_FILE", None)
34
+
35
+
36
+ @lru_cache()
37
+ def get_settings() -> Settings:
38
+ """Get cached application settings."""
39
+ return Settings()
backend/app/core/logging_config.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import logging.handlers
3
+ from app.core.config import get_settings
4
+
5
+
6
+ def setup_logging():
7
+ """Configure logging for the application."""
8
+ settings = get_settings()
9
+
10
+ # Create logger
11
+ logger = logging.getLogger()
12
+ logger.setLevel(getattr(logging, settings.LOG_LEVEL))
13
+
14
+ # Remove existing handlers
15
+ logger.handlers.clear()
16
+
17
+ # Console handler
18
+ console_handler = logging.StreamHandler()
19
+ console_handler.setLevel(getattr(logging, settings.LOG_LEVEL))
20
+ formatter = logging.Formatter(
21
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
22
+ )
23
+ console_handler.setFormatter(formatter)
24
+ logger.addHandler(console_handler)
25
+
26
+ # File handler (if specified)
27
+ if settings.LOG_FILE:
28
+ file_handler = logging.handlers.RotatingFileHandler(
29
+ settings.LOG_FILE,
30
+ maxBytes=10485760, # 10 MB
31
+ backupCount=5,
32
+ )
33
+ file_handler.setLevel(getattr(logging, settings.LOG_LEVEL))
34
+ file_handler.setFormatter(formatter)
35
+ logger.addHandler(file_handler)
backend/app/models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Data models and schemas."""
backend/app/models/schemas.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, HttpUrl, Field
2
+ from typing import Optional, Union, Literal
3
+
4
+
5
+ class TextAnalysisRequest(BaseModel):
6
+ content_type: Literal["text"]
7
+ text: str = Field(..., description="Text content to analyze for deepfake detection")
8
+ model: Optional[str] = Field(None, description="Detector model to use")
9
+
10
+ class Config:
11
+ json_schema_extra = {
12
+ "example": {
13
+ "content_type": "text",
14
+ "text": "Some text that might be AI-generated",
15
+ "model": "mock"
16
+ }
17
+ }
18
+
19
+
20
+ class ImageAnalysisRequest(BaseModel):
21
+ content_type: Literal["image"]
22
+ image_url: HttpUrl = Field(..., description="URL of the image to analyze")
23
+ model: Optional[str] = Field(None, description="Detector model to use")
24
+
25
+ class Config:
26
+ json_schema_extra = {
27
+ "example": {
28
+ "content_type": "image",
29
+ "image_url": "https://example.com/image.jpg",
30
+ "model": "mock"
31
+ }
32
+ }
33
+
34
+
35
+ class VideoAnalysisRequest(BaseModel):
36
+ content_type: Literal["video"]
37
+ video_url: HttpUrl = Field(..., description="URL of the video to analyze")
38
+ model: Optional[str] = Field(None, description="Detector model to use")
39
+
40
+ class Config:
41
+ json_schema_extra = {
42
+ "example": {
43
+ "content_type": "video",
44
+ "video_url": "https://example.com/video.mp4",
45
+ "model": "mock"
46
+ }
47
+ }
48
+
49
+
50
+ class FileAnalysisRequest(BaseModel):
51
+ content_type: Literal["file"]
52
+ file_url: HttpUrl = Field(..., description="URL of the file to analyze")
53
+ model: Optional[str] = Field(None, description="Detector model to use")
54
+
55
+ class Config:
56
+ json_schema_extra = {
57
+ "example": {
58
+ "content_type": "file",
59
+ "file_url": "https://example.com/video.mp4",
60
+ "model": "mock"
61
+ }
62
+ }
63
+
64
+
65
+ AnalysisRequest = Union[
66
+ TextAnalysisRequest,
67
+ ImageAnalysisRequest,
68
+ VideoAnalysisRequest,
69
+ FileAnalysisRequest,
70
+ ]
71
+
72
+
73
+ class AnalysisResponse(BaseModel):
74
+ is_deepfake: bool = Field(..., description="Whether the content is detected as a deepfake")
75
+ confidence: float = Field(..., ge=0.0, le=1.0, description="Confidence score between 0.0 and 1.0")
76
+ analysis_time: float = Field(..., description="Time taken for analysis in seconds")
77
+ model_used: str = Field(..., description="The detector model that was used")
78
+ content_type: str = Field(..., description="Type of content analyzed (text/image/video/file)")
79
+
80
+ class Config:
81
+ json_schema_extra = {
82
+ "example": {
83
+ "is_deepfake": True,
84
+ "confidence": 0.847,
85
+ "analysis_time": 1.234,
86
+ "model_used": "mock",
87
+ "content_type": "image"
88
+ }
89
+ }
90
+
91
+
92
+ class ErrorResponse(BaseModel):
93
+ error: str = Field(..., description="Error message")
94
+ status_code: int = Field(..., description="HTTP status code")
95
+ details: Optional[str] = Field(None, description="Additional error details")
96
+
97
+ class Config:
98
+ json_schema_extra = {
99
+ "example": {
100
+ "error": "Invalid URL format",
101
+ "status_code": 400,
102
+ "details": "The provided URL is not valid"
103
+ }
104
+ }
105
+
106
+
107
+ class HealthResponse(BaseModel):
108
+ status: str = Field(..., description="Service status")
109
+ service: str = Field(..., description="Service name")
110
+ version: str = Field(..., description="Service version")
111
+ available_models: list = Field(..., description="Available detector models")
112
+ supported_types: list = Field(..., description="Supported content types")
backend/app/services/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Service layer for business logic."""
backend/app/services/detector/__init__.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Detector models for deepfake detection."""
2
+
3
+ from app.services.detector.base import BaseDetector
4
+ from app.services.detector.mock import MockDetector
5
+
6
+ __all__ = ["BaseDetector", "MockDetector", "get_detector"]
7
+
8
+
9
+ def get_detector(model_name: str = "mock") -> BaseDetector:
10
+ """
11
+ Factory function to get detector instance by model name.
12
+
13
+ Args:
14
+ model_name: Name of the detector model
15
+
16
+ Returns:
17
+ Instance of the requested detector
18
+
19
+ Raises:
20
+ ValueError: If model is not supported
21
+ """
22
+ detectors = {
23
+ "mock": MockDetector,
24
+ # Future models:
25
+ # "deepseek": DeepseekDetector,
26
+ # "openai": OpenAIDetector,
27
+ # "huggingface": HuggingFaceDetector,
28
+ }
29
+
30
+ if model_name not in detectors:
31
+ available = ", ".join(detectors.keys())
32
+ raise ValueError(
33
+ f"Detector model '{model_name}' is not supported. "
34
+ f"Available models: {available}"
35
+ )
36
+
37
+ return detectors[model_name]()
backend/app/services/detector/base.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Base detector class defining the interface for all detectors."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Dict, Any
5
+
6
+
7
+ class BaseDetector(ABC):
8
+ """
9
+ Abstract base class for deepfake detectors.
10
+
11
+ All detector implementations should inherit from this class and implement
12
+ the detect() method.
13
+ """
14
+
15
+ def __init__(self, model_name: str):
16
+ """
17
+ Initialize the detector.
18
+
19
+ Args:
20
+ model_name: Name of the detector model
21
+ """
22
+ self.model_name = model_name
23
+
24
+ @abstractmethod
25
+ async def detect(self, file_bytes: bytes) -> Dict[str, Any]:
26
+ """
27
+ Detect if file is a deepfake.
28
+
29
+ Args:
30
+ file_bytes: The file contents as bytes
31
+
32
+ Returns:
33
+ Dictionary containing:
34
+ - is_deepfake: Boolean indicating if file is a deepfake
35
+ - confidence: Float between 0.0 and 1.0
36
+ - analysis_time: Float representing processing time
37
+ """
38
+ pass
backend/app/services/detector/mock.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Mock detector implementation for testing and development."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import time
6
+ from typing import Dict, Any
7
+
8
+ from app.services.detector.base import BaseDetector
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class MockDetector(BaseDetector):
14
+ """
15
+ Mock detector for testing and development.
16
+
17
+ Simulates deepfake detection without requiring actual ML models.
18
+ """
19
+
20
+ def __init__(self):
21
+ """Initialize the mock detector."""
22
+ super().__init__("mock")
23
+
24
+ async def detect(self, file_bytes: bytes) -> Dict[str, Any]:
25
+ """
26
+ Simulate deepfake detection with a random result.
27
+
28
+ Args:
29
+ file_bytes: The file contents as bytes
30
+
31
+ Returns:
32
+ Dictionary with is_deepfake, confidence, and analysis_time
33
+ """
34
+ logger.info("Starting mock deepfake analysis...")
35
+
36
+ start_time = time.time()
37
+
38
+ # Simulate processing delay (1 to 2 seconds)
39
+ delay = 1.0 + (hash(file_bytes) % 100) / 100.0
40
+ await asyncio.sleep(delay)
41
+
42
+ analysis_time = time.time() - start_time
43
+
44
+ # Simulate ML model output (deterministic based on file content hash)
45
+ file_hash = hash(file_bytes) % 100
46
+ is_deepfake = file_hash > 50 # ~50% chance
47
+ confidence = (file_hash % 100) / 100.0
48
+
49
+ result = {
50
+ "is_deepfake": is_deepfake,
51
+ "confidence": round(confidence, 3),
52
+ "analysis_time": round(analysis_time, 3),
53
+ }
54
+
55
+ logger.info(f"Mock analysis completed. Result: {result}")
56
+ return result
backend/app/services/download.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import httpx
3
+
4
+ from app.core.config import get_settings
5
+ from app.utils.exceptions import (
6
+ FileDownloadError,
7
+ InvalidURLError,
8
+ DownloadTimeoutError,
9
+ FileSizeError,
10
+ )
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ async def download_file(file_url: str) -> bytes:
16
+ """
17
+ Asynchronously download a file from the given URL.
18
+
19
+ Args:
20
+ file_url: The URL of the file to download
21
+
22
+ Returns:
23
+ The file contents as bytes
24
+
25
+ Raises:
26
+ InvalidURLError: If the URL is invalid
27
+ DownloadTimeoutError: If download times out
28
+ FileSizeError: If file size exceeds limit
29
+ FileDownloadError: If download fails for other reasons
30
+ """
31
+ settings = get_settings()
32
+ logger.info(f"Starting file download from: {file_url}")
33
+
34
+ try:
35
+ async with httpx.AsyncClient(timeout=settings.DOWNLOAD_TIMEOUT) as client:
36
+ response = await client.get(file_url, follow_redirects=True)
37
+
38
+ # Check for HTTP errors
39
+ if response.status_code != 200:
40
+ logger.error(
41
+ f"Failed to download file. Status code: {response.status_code}"
42
+ )
43
+ raise FileDownloadError(
44
+ f"Failed to download file. Server returned status code {response.status_code}",
45
+ status_code=response.status_code,
46
+ )
47
+
48
+ # Check content length header
49
+ content_length = response.headers.get("content-length")
50
+ if content_length and int(content_length) > settings.MAX_FILE_SIZE:
51
+ logger.error(
52
+ f"File size exceeds maximum allowed size: {content_length} bytes"
53
+ )
54
+ raise FileSizeError(
55
+ f"File size exceeds maximum allowed size of {settings.MAX_FILE_SIZE} bytes"
56
+ )
57
+
58
+ file_bytes = response.content
59
+
60
+ # Check actual content size
61
+ if len(file_bytes) > settings.MAX_FILE_SIZE:
62
+ logger.error(
63
+ f"Downloaded file exceeds maximum size: {len(file_bytes)} bytes"
64
+ )
65
+ raise FileSizeError(
66
+ f"File size exceeds maximum allowed size of {settings.MAX_FILE_SIZE} bytes"
67
+ )
68
+
69
+ logger.info(
70
+ f"File download completed successfully. Size: {len(file_bytes)} bytes"
71
+ )
72
+ return file_bytes
73
+
74
+ except httpx.InvalidURL as e:
75
+ logger.error(f"Invalid URL provided: {file_url} - {str(e)}")
76
+ raise InvalidURLError("Invalid URL format")
77
+ except httpx.TimeoutException as e:
78
+ logger.error(f"Download timeout for URL: {file_url}")
79
+ raise DownloadTimeoutError("File download timed out. Please try again with a faster source.")
80
+ except (FileSizeError, InvalidURLError, DownloadTimeoutError):
81
+ # Re-raise custom exceptions
82
+ raise
83
+ except httpx.RequestError as e:
84
+ logger.error(f"Failed to download file from {file_url}: {str(e)}")
85
+ raise FileDownloadError(
86
+ "Failed to download file from the provided URL. Please check the URL and try again."
87
+ )
88
+ except Exception as e:
89
+ logger.error(f"Unexpected error during file download: {str(e)}")
90
+ raise FileDownloadError(
91
+ "An unexpected error occurred during file download."
92
+ )
backend/app/services/image_analyzer.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import time
3
+ from typing import Dict, Any
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ async def analyze_image(image_bytes: bytes) -> Dict[str, Any]:
9
+ start_time = time.time()
10
+
11
+ logger.info(f"Starting image analysis, size: {len(image_bytes)} bytes")
12
+
13
+ image_hash = hash(image_bytes) % 100
14
+ is_deepfake = image_hash > 50
15
+ confidence = (image_hash % 100) / 100.0
16
+
17
+ analysis_time = time.time() - start_time
18
+
19
+ result = {
20
+ "is_deepfake": is_deepfake,
21
+ "confidence": round(confidence, 3),
22
+ "analysis_time": round(analysis_time, 3),
23
+ }
24
+
25
+ logger.info(f"Image analysis completed. Result: {result}")
26
+ return result
backend/app/services/queue.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Optional, Any, Dict
3
+ import json
4
+
5
+ from app.core.config import get_settings
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ class QueueService:
11
+ """
12
+ Queue service for managing asynchronous analysis tasks.
13
+
14
+ Currently uses in-memory queue, can be extended to use Redis.
15
+ """
16
+
17
+ def __init__(self):
18
+ """Initialize the queue service."""
19
+ self.settings = get_settings()
20
+ self.redis_client = None
21
+
22
+ if self.settings.REDIS_ENABLED:
23
+ self._initialize_redis()
24
+
25
+ def _initialize_redis(self):
26
+ """Initialize Redis connection (future implementation)."""
27
+ # This will be implemented when Redis support is added
28
+ logger.info(
29
+ f"Redis queue service initialized: {self.settings.REDIS_URL}"
30
+ )
31
+
32
+ async def enqueue_analysis(
33
+ self,
34
+ file_url: str,
35
+ model: str,
36
+ task_id: str,
37
+ ) -> bool:
38
+ """
39
+ Enqueue an analysis task.
40
+
41
+ Args:
42
+ file_url: URL of the file to analyze
43
+ model: Detector model to use
44
+ task_id: Unique task identifier
45
+
46
+ Returns:
47
+ True if successful, False otherwise
48
+ """
49
+ task_data = {
50
+ "task_id": task_id,
51
+ "file_url": file_url,
52
+ "model": model,
53
+ }
54
+
55
+ logger.info(f"Enqueuing analysis task: {task_id}")
56
+
57
+ if self.settings.REDIS_ENABLED:
58
+ # Future: Push to Redis queue
59
+ # await self.redis_client.lpush(
60
+ # self.settings.REDIS_QUEUE_NAME,
61
+ # json.dumps(task_data)
62
+ # )
63
+ pass
64
+
65
+ return True
66
+
67
+ async def get_task_result(self, task_id: str) -> Optional[Dict[str, Any]]:
68
+ """
69
+ Get analysis result for a task.
70
+
71
+ Args:
72
+ task_id: Task identifier
73
+
74
+ Returns:
75
+ Analysis result or None if not found
76
+ """
77
+ logger.info(f"Retrieving result for task: {task_id}")
78
+
79
+ if self.settings.REDIS_ENABLED:
80
+ # Future: Get from Redis
81
+ # result = await self.redis_client.get(f"result:{task_id}")
82
+ # return json.loads(result) if result else None
83
+ pass
84
+
85
+ return None
86
+
87
+
88
+ # Singleton instance
89
+ _queue_service: Optional[QueueService] = None
90
+
91
+
92
+ def get_queue_service() -> QueueService:
93
+ """Get or create the queue service singleton."""
94
+ global _queue_service
95
+ if _queue_service is None:
96
+ _queue_service = QueueService()
97
+ return _queue_service
backend/app/services/text_analyzer.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import time
3
+ from typing import Dict, Any
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+
8
+ async def analyze_text(text: str) -> Dict[str, Any]:
9
+ start_time = time.time()
10
+
11
+ logger.info(f"Starting text analysis, length: {len(text)} chars")
12
+
13
+ text_hash = hash(text) % 100
14
+ is_deepfake = text_hash > 50
15
+ confidence = (text_hash % 100) / 100.0
16
+
17
+ analysis_time = time.time() - start_time
18
+
19
+ result = {
20
+ "is_deepfake": is_deepfake,
21
+ "confidence": round(confidence, 3),
22
+ "analysis_time": round(analysis_time, 3),
23
+ }
24
+
25
+ logger.info(f"Text analysis completed. Result: {result}")
26
+ return result
backend/app/utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Utility functions and exception classes."""
backend/app/utils/exceptions.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Custom exception classes."""
2
+
3
+
4
+ class DeepfakeDetectionError(Exception):
5
+ """Base exception for deepfake detection service."""
6
+
7
+ def __init__(self, message: str, status_code: int = 500):
8
+ self.message = message
9
+ self.status_code = status_code
10
+ super().__init__(self.message)
11
+
12
+
13
+ class FileDownloadError(DeepfakeDetectionError):
14
+ """Exception raised when file download fails."""
15
+
16
+ def __init__(self, message: str, status_code: int = 400):
17
+ super().__init__(message, status_code)
18
+
19
+
20
+ class InvalidURLError(DeepfakeDetectionError):
21
+ """Exception raised when URL is invalid."""
22
+
23
+ def __init__(self, message: str = "Invalid URL format"):
24
+ super().__init__(message, 400)
25
+
26
+
27
+ class DownloadTimeoutError(DeepfakeDetectionError):
28
+ """Exception raised when file download times out."""
29
+
30
+ def __init__(self, message: str = "File download timed out"):
31
+ super().__init__(message, 408)
32
+
33
+
34
+ class FileSizeError(DeepfakeDetectionError):
35
+ """Exception raised when file size exceeds limit."""
36
+
37
+ def __init__(self, message: str = "File size exceeds maximum allowed size"):
38
+ super().__init__(message, 400)
39
+
40
+
41
+ class DetectorError(DeepfakeDetectionError):
42
+ """Exception raised when detector model fails."""
43
+
44
+ def __init__(self, message: str = "Deepfake detection failed"):
45
+ super().__init__(message, 500)
46
+
47
+
48
+ class UnsupportedModelError(DeepfakeDetectionError):
49
+ """Exception raised when requested model is not supported."""
50
+
51
+ def __init__(self, model_name: str):
52
+ message = f"Detector model '{model_name}' is not supported"
53
+ super().__init__(message, 400)
backend/main.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import uvicorn
2
+ from app import app
3
+
4
+ if __name__ == "__main__":
5
+ uvicorn.run(app, host="127.0.0.1", port=8000)
backend/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.115.0
2
+ uvicorn[standard]==0.30.0
3
+ httpx==0.27.0
4
+ pydantic==2.8.2
5
+ pydantic-settings==2.3.1
6
+ python-multipart==0.0.6
backend/setup.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import subprocess
4
+
5
+
6
+ def run_command(command, description):
7
+ """Run a command and handle errors."""
8
+ print(f"\n{'='*60}")
9
+ print(f"πŸ“ {description}")
10
+ print(f"{'='*60}")
11
+ try:
12
+ result = subprocess.run(command, shell=True, check=True)
13
+ return result.returncode == 0
14
+ except subprocess.CalledProcessError as e:
15
+ print(f"❌ Error: {description} failed")
16
+ return False
17
+
18
+
19
+ def main():
20
+ """Setup the development environment."""
21
+ print("\n" + "="*60)
22
+ print("πŸš€ Deepfake Detection Service - Backend Setup")
23
+ print("="*60)
24
+ print(f"βœ… Python version: {sys.version}")
25
+
26
+ # Determine OS for venv activation
27
+ is_windows = sys.platform == "win32"
28
+ venv_path = "venv"
29
+
30
+ # Create virtual environment
31
+ if not run_command(
32
+ f"{sys.executable} -m venv {venv_path}",
33
+ "Creating virtual environment"
34
+ ):
35
+ sys.exit(1)
36
+
37
+ # Activate venv and install dependencies
38
+ if is_windows:
39
+ activate_cmd = f"{venv_path}\\Scripts\\activate && pip install -r requirements.txt"
40
+ else:
41
+ activate_cmd = f"source {venv_path}/bin/activate && pip install -r requirements.txt"
42
+
43
+ if not run_command(activate_cmd, "Installing dependencies"):
44
+ sys.exit(1)
45
+
46
+ # Create .env file if it doesn't exist
47
+ if not os.path.exists(".env"):
48
+ run_command("copy .env.example .env" if is_windows else "cp .env.example .env",
49
+ "Creating .env file from template")
50
+
51
+ print("\n" + "="*60)
52
+ print("βœ… Setup completed successfully!")
53
+ print("="*60)
54
+ print("\nπŸ“ Next steps:")
55
+ print(f" 1. Activate virtual environment:")
56
+ if is_windows:
57
+ print(f" {venv_path}\\Scripts\\activate")
58
+ else:
59
+ print(f" source {venv_path}/bin/activate")
60
+ print(f"\n 2. Start the server:")
61
+ print(f" python main.py")
62
+ print(f"\n 3. Visit http://127.0.0.1:8000/docs for interactive API docs")
63
+ print(f"\n 4. Check .env file for configuration options")
64
+ print("\n" + "="*60)
65
+
66
+
67
+ if __name__ == "__main__":
68
+ main()