Spaces:
Sleeping
Sleeping
Upload 19 files
Browse files- .dockerignore +16 -0
- .gitattributes +35 -35
- .gitignore +169 -0
- Dockerfile +33 -21
- README.md +105 -20
- USAGE_GUIDE.md +152 -0
- api.py +385 -0
- app.py +451 -0
- app_hf.py +272 -0
- classify_audio.py +122 -0
- config.json +100 -0
- deploy.ps1 +68 -0
- deploy.sh +51 -0
- docker-compose.yml +34 -0
- env.example +19 -0
- pytorch_model.bin +3 -0
- requirements.txt +8 -3
- requirements_hf.txt +9 -0
- test_api.py +183 -0
.dockerignore
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.git
|
2 |
+
.gitignore
|
3 |
+
README.md
|
4 |
+
USAGE_GUIDE.md
|
5 |
+
test_api.py
|
6 |
+
*.md
|
7 |
+
.DS_Store
|
8 |
+
__pycache__
|
9 |
+
*.pyc
|
10 |
+
*.pyo
|
11 |
+
*.pyd
|
12 |
+
.env
|
13 |
+
.venv
|
14 |
+
venv/
|
15 |
+
.pytest_cache
|
16 |
+
.coverage
|
.gitattributes
CHANGED
@@ -1,35 +1,35 @@
|
|
1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
share/python-wheels/
|
20 |
+
*.egg-info/
|
21 |
+
.installed.cfg
|
22 |
+
*.egg
|
23 |
+
MANIFEST
|
24 |
+
|
25 |
+
# PyInstaller
|
26 |
+
*.manifest
|
27 |
+
*.spec
|
28 |
+
|
29 |
+
# Installer logs
|
30 |
+
pip-log.txt
|
31 |
+
pip-delete-this-directory.txt
|
32 |
+
|
33 |
+
# Unit test / coverage reports
|
34 |
+
htmlcov/
|
35 |
+
.tox/
|
36 |
+
.nox/
|
37 |
+
.coverage
|
38 |
+
.coverage.*
|
39 |
+
.cache
|
40 |
+
nosetests.xml
|
41 |
+
coverage.xml
|
42 |
+
*.cover
|
43 |
+
*.py,cover
|
44 |
+
.hypothesis/
|
45 |
+
.pytest_cache/
|
46 |
+
cover/
|
47 |
+
|
48 |
+
# Translations
|
49 |
+
*.mo
|
50 |
+
*.pot
|
51 |
+
|
52 |
+
# Django stuff:
|
53 |
+
*.log
|
54 |
+
local_settings.py
|
55 |
+
db.sqlite3
|
56 |
+
db.sqlite3-journal
|
57 |
+
|
58 |
+
# Flask stuff:
|
59 |
+
instance/
|
60 |
+
.webassets-cache
|
61 |
+
|
62 |
+
# Scrapy stuff:
|
63 |
+
.scrapy
|
64 |
+
|
65 |
+
# Sphinx documentation
|
66 |
+
docs/_build/
|
67 |
+
|
68 |
+
# PyBuilder
|
69 |
+
.pybuilder/
|
70 |
+
target/
|
71 |
+
|
72 |
+
# Jupyter Notebook
|
73 |
+
.ipynb_checkpoints
|
74 |
+
|
75 |
+
# IPython
|
76 |
+
profile_default/
|
77 |
+
ipython_config.py
|
78 |
+
|
79 |
+
# pyenv
|
80 |
+
.python-version
|
81 |
+
|
82 |
+
# pipenv
|
83 |
+
Pipfile.lock
|
84 |
+
|
85 |
+
# poetry
|
86 |
+
poetry.lock
|
87 |
+
|
88 |
+
# pdm
|
89 |
+
.pdm.toml
|
90 |
+
|
91 |
+
# PEP 582
|
92 |
+
__pypackages__/
|
93 |
+
|
94 |
+
# Celery stuff
|
95 |
+
celerybeat-schedule
|
96 |
+
celerybeat.pid
|
97 |
+
|
98 |
+
# SageMath parsed files
|
99 |
+
*.sage.py
|
100 |
+
|
101 |
+
# Environments
|
102 |
+
.env
|
103 |
+
.venv
|
104 |
+
env/
|
105 |
+
venv/
|
106 |
+
ENV/
|
107 |
+
env.bak/
|
108 |
+
venv.bak/
|
109 |
+
|
110 |
+
# Spyder project settings
|
111 |
+
.spyderproject
|
112 |
+
.spyproject
|
113 |
+
|
114 |
+
# Rope project settings
|
115 |
+
.ropeproject
|
116 |
+
|
117 |
+
# mkdocs documentation
|
118 |
+
/site
|
119 |
+
|
120 |
+
# mypy
|
121 |
+
.mypy_cache/
|
122 |
+
.dmypy.json
|
123 |
+
dmypy.json
|
124 |
+
|
125 |
+
# Pyre type checker
|
126 |
+
.pyre/
|
127 |
+
|
128 |
+
# pytype static type analyzer
|
129 |
+
.pytype/
|
130 |
+
|
131 |
+
# Cython debug symbols
|
132 |
+
cython_debug/
|
133 |
+
|
134 |
+
# PyCharm
|
135 |
+
.idea/
|
136 |
+
|
137 |
+
# VS Code
|
138 |
+
.vscode/
|
139 |
+
|
140 |
+
# OS generated files
|
141 |
+
.DS_Store
|
142 |
+
.DS_Store?
|
143 |
+
._*
|
144 |
+
.Spotlight-V100
|
145 |
+
.Trashes
|
146 |
+
ehthumbs.db
|
147 |
+
Thumbs.db
|
148 |
+
|
149 |
+
# Audio files (test files)
|
150 |
+
*.wav
|
151 |
+
*.mp3
|
152 |
+
*.flac
|
153 |
+
*.m4a
|
154 |
+
*.ogg
|
155 |
+
|
156 |
+
# Temporary files
|
157 |
+
*.tmp
|
158 |
+
*.temp
|
159 |
+
temp/
|
160 |
+
tmp/
|
161 |
+
|
162 |
+
# Logs
|
163 |
+
*.log
|
164 |
+
logs/
|
165 |
+
|
166 |
+
# API keys and secrets
|
167 |
+
.env
|
168 |
+
secrets.json
|
169 |
+
config.local.json
|
Dockerfile
CHANGED
@@ -1,21 +1,33 @@
|
|
1 |
-
FROM python:3.
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
&& rm -rf /var/lib/apt/lists/*
|
11 |
-
|
12 |
-
|
13 |
-
COPY
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
# Set working directory
|
4 |
+
WORKDIR /app
|
5 |
+
|
6 |
+
# Install system dependencies
|
7 |
+
RUN apt-get update && apt-get install -y \
|
8 |
+
ffmpeg \
|
9 |
+
libsndfile1 \
|
10 |
+
&& rm -rf /var/lib/apt/lists/*
|
11 |
+
|
12 |
+
# Copy requirements first for better caching
|
13 |
+
COPY requirements.txt .
|
14 |
+
|
15 |
+
# Install Python dependencies
|
16 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
17 |
+
|
18 |
+
# Copy application files
|
19 |
+
COPY . .
|
20 |
+
|
21 |
+
# Create non-root user for security
|
22 |
+
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
|
23 |
+
USER appuser
|
24 |
+
|
25 |
+
# Expose port
|
26 |
+
EXPOSE 8000
|
27 |
+
|
28 |
+
# Health check
|
29 |
+
HEALTHCHECK --interval=30s --timeout=30s --start-period=60s --retries=3 \
|
30 |
+
CMD curl -f http://localhost:8000/health || exit 1
|
31 |
+
|
32 |
+
# Start the application
|
33 |
+
CMD ["python", "api.py"]
|
README.md
CHANGED
@@ -1,20 +1,105 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Madverse Music: AI Music Detection
|
2 |
+
|
3 |
+
Detect AI-generated music vs human-created music using advanced AI technology.
|
4 |
+
|
5 |
+
## What is Madverse Music?
|
6 |
+
|
7 |
+
Madverse Music is an AI-powered tool that can detect whether music is created by AI or human artists. With the growth of AI music generation platforms like Suno and Udio, it's important to distinguish between human creativity and artificial intelligence.
|
8 |
+
|
9 |
+
## Key Features
|
10 |
+
|
11 |
+
- Upload and analyze audio files instantly
|
12 |
+
- Web interface for easy testing
|
13 |
+
- REST API for integration
|
14 |
+
- High accuracy: 97% F1 score with 96% sensitivity and 99% specificity
|
15 |
+
- Supports WAV, MP3, FLAC, M4A, and OGG files
|
16 |
+
- Uses SpecTTTra transformer technology
|
17 |
+
- Trained on 97,000+ songs
|
18 |
+
|
19 |
+
## Quick Start
|
20 |
+
|
21 |
+
### Hugging Face Space (Recommended)
|
22 |
+
1. Go to our Hugging Face Space
|
23 |
+
2. Upload your audio file
|
24 |
+
3. Get instant results
|
25 |
+
|
26 |
+
### Local Setup
|
27 |
+
```bash
|
28 |
+
# Install dependencies
|
29 |
+
pip install -r requirements.txt
|
30 |
+
|
31 |
+
# Start web interface
|
32 |
+
streamlit run app.py
|
33 |
+
|
34 |
+
# Or start API server
|
35 |
+
python api.py
|
36 |
+
```
|
37 |
+
|
38 |
+
## Web Interface
|
39 |
+
|
40 |
+
Use the web interface for easy testing:
|
41 |
+
|
42 |
+
```bash
|
43 |
+
streamlit run app.py
|
44 |
+
```
|
45 |
+
|
46 |
+
Then open your browser to `http://localhost:8501` and:
|
47 |
+
1. Upload your audio file
|
48 |
+
2. Click "Analyze Audio"
|
49 |
+
3. Get instant results
|
50 |
+
|
51 |
+
The web interface provides:
|
52 |
+
- Audio player to preview your file
|
53 |
+
- Detailed analysis with confidence scores
|
54 |
+
- Clean visualization of results
|
55 |
+
|
56 |
+
## Model Performance
|
57 |
+
|
58 |
+
Our model (SpecTTTra-Ξ± 120s) achieves:
|
59 |
+
- F1 Score: 0.97
|
60 |
+
- Sensitivity: 0.96
|
61 |
+
- Specificity: 0.99
|
62 |
+
|
63 |
+
## Technical Details
|
64 |
+
|
65 |
+
- Model: SpecTTTra (Spectro-Temporal Tokens Transformer)
|
66 |
+
- Sample Rate: 16kHz
|
67 |
+
- Max Duration: 120 seconds
|
68 |
+
- Trained on 97,000+ songs
|
69 |
+
|
70 |
+
## API Usage
|
71 |
+
|
72 |
+
### Hugging Face Space
|
73 |
+
```bash
|
74 |
+
# Health check
|
75 |
+
curl https://your-space-name.hf.space/health
|
76 |
+
|
77 |
+
# Analyze audio file
|
78 |
+
curl -X POST "https://your-space-name.hf.space/analyze" \
|
79 |
+
-F "[email protected]"
|
80 |
+
```
|
81 |
+
|
82 |
+
### Local Setup
|
83 |
+
```bash
|
84 |
+
# Install dependencies
|
85 |
+
pip install -r requirements.txt
|
86 |
+
|
87 |
+
# Start the API server
|
88 |
+
python api.py
|
89 |
+
```
|
90 |
+
|
91 |
+
### Command Line Usage
|
92 |
+
|
93 |
+
```bash
|
94 |
+
# Analyze a single file
|
95 |
+
python classify_audio.py "your_song.mp3"
|
96 |
+
|
97 |
+
# Analyze all files in directory
|
98 |
+
python classify_audio.py
|
99 |
+
```
|
100 |
+
|
101 |
+
## Acknowledgments
|
102 |
+
|
103 |
+
This tool is designed for research, education, and transparency in AI music detection. Results may vary depending on audio quality and content type.
|
104 |
+
|
105 |
+
Visit [madverse.co](https://madverse.co) for more information.
|
USAGE_GUIDE.md
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Madverse Music: AI Audio Classifier - Usage Guide
|
2 |
+
|
3 |
+
## Quick Start
|
4 |
+
|
5 |
+
### Option 1: Hugging Face Space (Recommended)
|
6 |
+
Use our deployed model on Hugging Face Spaces:
|
7 |
+
|
8 |
+
**Web Interface:**
|
9 |
+
1. Go to the Hugging Face Space URL
|
10 |
+
2. Upload your audio file
|
11 |
+
3. Click "Analyze Audio"
|
12 |
+
4. Get instant results
|
13 |
+
|
14 |
+
**API Access:**
|
15 |
+
```bash
|
16 |
+
# Health check
|
17 |
+
curl https://your-space-name.hf.space/health
|
18 |
+
|
19 |
+
# Analyze audio file
|
20 |
+
curl -X POST "https://your-space-name.hf.space/analyze" \
|
21 |
+
-F "[email protected]"
|
22 |
+
```
|
23 |
+
|
24 |
+
### Option 2: Local Setup
|
25 |
+
```bash
|
26 |
+
# Install dependencies
|
27 |
+
pip install -r requirements.txt
|
28 |
+
|
29 |
+
# Start the API server
|
30 |
+
python api.py
|
31 |
+
|
32 |
+
# Or start web interface
|
33 |
+
streamlit run app.py
|
34 |
+
```
|
35 |
+
|
36 |
+
## Supported Audio Formats
|
37 |
+
- WAV (.wav)
|
38 |
+
- MP3 (.mp3)
|
39 |
+
- FLAC (.flac)
|
40 |
+
- M4A (.m4a)
|
41 |
+
- OGG (.ogg)
|
42 |
+
|
43 |
+
## API Usage
|
44 |
+
|
45 |
+
### Hugging Face Space API
|
46 |
+
|
47 |
+
#### Health Check
|
48 |
+
```bash
|
49 |
+
GET /health
|
50 |
+
```
|
51 |
+
|
52 |
+
#### Analyze Audio
|
53 |
+
```bash
|
54 |
+
POST /analyze
|
55 |
+
```
|
56 |
+
Upload audio file using multipart/form-data
|
57 |
+
|
58 |
+
**Request:**
|
59 |
+
Upload file using form data with field name "file"
|
60 |
+
|
61 |
+
**Response Format:**
|
62 |
+
```json
|
63 |
+
{
|
64 |
+
"classification": "Real",
|
65 |
+
"confidence": 0.85,
|
66 |
+
"probability": 0.15,
|
67 |
+
"raw_score": -1.73,
|
68 |
+
"duration": 30.5,
|
69 |
+
"message": "Detected as real music"
|
70 |
+
}
|
71 |
+
```
|
72 |
+
|
73 |
+
|
74 |
+
|
75 |
+
### Usage Examples
|
76 |
+
|
77 |
+
#### Python
|
78 |
+
```python
|
79 |
+
import requests
|
80 |
+
|
81 |
+
# Upload file to HF Space
|
82 |
+
with open('your_song.mp3', 'rb') as f:
|
83 |
+
response = requests.post('https://your-space-name.hf.space/analyze',
|
84 |
+
files={'file': f})
|
85 |
+
result = response.json()
|
86 |
+
print(result)
|
87 |
+
```
|
88 |
+
|
89 |
+
#### JavaScript
|
90 |
+
```javascript
|
91 |
+
const formData = new FormData();
|
92 |
+
formData.append('file', fileInput.files[0]);
|
93 |
+
|
94 |
+
const response = await fetch('https://your-space-name.hf.space/analyze', {
|
95 |
+
method: 'POST',
|
96 |
+
body: formData
|
97 |
+
});
|
98 |
+
const result = await response.json();
|
99 |
+
```
|
100 |
+
|
101 |
+
## Understanding Results
|
102 |
+
|
103 |
+
The classifier will output:
|
104 |
+
- **"Real"** = Human-created music
|
105 |
+
- **"Fake"** = AI-generated music (from Suno, Udio, etc.)
|
106 |
+
|
107 |
+
### API Response Format:
|
108 |
+
```json
|
109 |
+
{
|
110 |
+
"classification": "Real",
|
111 |
+
"confidence": 0.85,
|
112 |
+
"probability": 0.15,
|
113 |
+
"raw_score": -1.73,
|
114 |
+
"duration": 30.5,
|
115 |
+
"message": "Detected as real music"
|
116 |
+
}
|
117 |
+
```
|
118 |
+
|
119 |
+
### Command Line Output:
|
120 |
+
```
|
121 |
+
Analyzing: my_song.wav
|
122 |
+
Result: Fake (AI-generated music)
|
123 |
+
Confidence: 0.96 | Raw output: 3.786
|
124 |
+
```
|
125 |
+
|
126 |
+
## Model Specifications
|
127 |
+
- Model: SpecTTTra-Ξ± (120 seconds)
|
128 |
+
- Sample Rate: 16kHz
|
129 |
+
- Performance: 97% F1 score, 96% sensitivity, 99% specificity
|
130 |
+
- Max Duration: 120 seconds (2 minutes)
|
131 |
+
|
132 |
+
## Technical Details
|
133 |
+
|
134 |
+
### How It Works:
|
135 |
+
1. Audio is loaded and resampled to 16kHz
|
136 |
+
2. Converted to mel-spectrograms
|
137 |
+
3. Processed by the SpecTTTra transformer model
|
138 |
+
4. Output logit is converted to probability using sigmoid
|
139 |
+
5. Classification: `prob < 0.5` = Real, `prob β₯ 0.5` = Fake
|
140 |
+
|
141 |
+
### Testing Your Music
|
142 |
+
|
143 |
+
1. Get AI-generated samples: Download from Suno, Udio, or other AI music platforms
|
144 |
+
2. Get real music samples: Use traditional human-created songs
|
145 |
+
3. Run the classifier: Compare results to see how well it detects AI vs human music
|
146 |
+
|
147 |
+
## Expected Performance
|
148 |
+
- High accuracy on detecting modern AI-generated music
|
149 |
+
- Works best with full songs (up to 120 seconds)
|
150 |
+
- Optimized for music from platforms like Suno and Udio
|
151 |
+
|
152 |
+
Note: This model was trained specifically for detecting AI-generated songs, not just AI vocals over real instrumentals. It analyzes the entire musical composition.
|
api.py
ADDED
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Madverse Music API
|
4 |
+
AI Music Detection Service
|
5 |
+
"""
|
6 |
+
|
7 |
+
from fastapi import FastAPI, HTTPException, BackgroundTasks, Header, Depends
|
8 |
+
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
9 |
+
from pydantic import BaseModel, HttpUrl
|
10 |
+
import torch
|
11 |
+
import librosa
|
12 |
+
import tempfile
|
13 |
+
import os
|
14 |
+
import requests
|
15 |
+
from pathlib import Path
|
16 |
+
import time
|
17 |
+
from typing import Optional, Annotated, List
|
18 |
+
import uvicorn
|
19 |
+
import asyncio
|
20 |
+
|
21 |
+
# Initialize FastAPI app
|
22 |
+
app = FastAPI(
|
23 |
+
title="Madverse Music API",
|
24 |
+
description="AI-powered music detection API to identify AI-generated vs human-created music",
|
25 |
+
version="1.0.0",
|
26 |
+
docs_url="/",
|
27 |
+
redoc_url="/docs"
|
28 |
+
)
|
29 |
+
|
30 |
+
# API Key Configuration
|
31 |
+
API_KEY = os.getenv("MADVERSE_API_KEY", "madverse-music-api-key-2024") # Default key for demo
|
32 |
+
|
33 |
+
# Global model variable
|
34 |
+
model = None
|
35 |
+
|
36 |
+
async def verify_api_key(x_api_key: Annotated[str | None, Header()] = None):
|
37 |
+
"""Verify API key from header"""
|
38 |
+
if x_api_key is None:
|
39 |
+
raise HTTPException(
|
40 |
+
status_code=401,
|
41 |
+
detail="Missing API key. Please provide a valid X-API-Key header."
|
42 |
+
)
|
43 |
+
if x_api_key != API_KEY:
|
44 |
+
raise HTTPException(
|
45 |
+
status_code=401,
|
46 |
+
detail="Invalid API key. Please provide a valid X-API-Key header."
|
47 |
+
)
|
48 |
+
return x_api_key
|
49 |
+
|
50 |
+
class MusicAnalysisRequest(BaseModel):
|
51 |
+
urls: List[HttpUrl]
|
52 |
+
|
53 |
+
def check_api_key_first(request: MusicAnalysisRequest, x_api_key: Annotated[str | None, Header()] = None):
|
54 |
+
"""Check API key before processing request"""
|
55 |
+
if x_api_key is None:
|
56 |
+
raise HTTPException(
|
57 |
+
status_code=401,
|
58 |
+
detail="Missing API key. Please provide a valid X-API-Key header."
|
59 |
+
)
|
60 |
+
if x_api_key != API_KEY:
|
61 |
+
raise HTTPException(
|
62 |
+
status_code=401,
|
63 |
+
detail="Invalid API key. Please provide a valid X-API-Key header."
|
64 |
+
)
|
65 |
+
return request
|
66 |
+
|
67 |
+
class FileAnalysisResult(BaseModel):
|
68 |
+
url: str
|
69 |
+
success: bool
|
70 |
+
classification: Optional[str] = None # "Real" or "Fake"
|
71 |
+
confidence: Optional[float] = None # 0.0 to 1.0
|
72 |
+
probability: Optional[float] = None # Raw sigmoid probability
|
73 |
+
raw_score: Optional[float] = None # Raw model output
|
74 |
+
duration: Optional[float] = None # Audio duration in seconds
|
75 |
+
message: str
|
76 |
+
processing_time: Optional[float] = None
|
77 |
+
error: Optional[str] = None
|
78 |
+
|
79 |
+
class MusicAnalysisResponse(BaseModel):
|
80 |
+
success: bool
|
81 |
+
total_files: int
|
82 |
+
successful_analyses: int
|
83 |
+
failed_analyses: int
|
84 |
+
results: List[FileAnalysisResult]
|
85 |
+
total_processing_time: float
|
86 |
+
message: str
|
87 |
+
|
88 |
+
class ErrorResponse(BaseModel):
|
89 |
+
success: bool
|
90 |
+
error: str
|
91 |
+
message: str
|
92 |
+
|
93 |
+
@app.on_event("startup")
|
94 |
+
async def load_model():
|
95 |
+
"""Load the AI model on startup"""
|
96 |
+
global model
|
97 |
+
try:
|
98 |
+
from sonics import HFAudioClassifier
|
99 |
+
print("π Loading Madverse Music AI model...")
|
100 |
+
model = HFAudioClassifier.from_pretrained("awsaf49/sonics-spectttra-alpha-120s")
|
101 |
+
model.eval()
|
102 |
+
print("β
Model loaded successfully!")
|
103 |
+
except Exception as e:
|
104 |
+
print(f"β Failed to load model: {e}")
|
105 |
+
raise
|
106 |
+
|
107 |
+
def cleanup_file(file_path: str):
|
108 |
+
"""Background task to cleanup temporary files"""
|
109 |
+
try:
|
110 |
+
if os.path.exists(file_path):
|
111 |
+
os.unlink(file_path)
|
112 |
+
except:
|
113 |
+
pass
|
114 |
+
|
115 |
+
def download_audio(url: str, max_size_mb: int = 100) -> str:
|
116 |
+
"""Download audio file from URL with size validation"""
|
117 |
+
try:
|
118 |
+
# Check if URL is accessible
|
119 |
+
response = requests.head(str(url), timeout=10)
|
120 |
+
|
121 |
+
# Check content size
|
122 |
+
content_length = response.headers.get('Content-Length')
|
123 |
+
if content_length and int(content_length) > max_size_mb * 1024 * 1024:
|
124 |
+
raise HTTPException(
|
125 |
+
status_code=413,
|
126 |
+
detail=f"File too large. Maximum size: {max_size_mb}MB"
|
127 |
+
)
|
128 |
+
|
129 |
+
# Download file
|
130 |
+
response = requests.get(str(url), timeout=30, stream=True)
|
131 |
+
response.raise_for_status()
|
132 |
+
|
133 |
+
# Create temporary file
|
134 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.tmp') as tmp_file:
|
135 |
+
downloaded_size = 0
|
136 |
+
for chunk in response.iter_content(chunk_size=8192):
|
137 |
+
downloaded_size += len(chunk)
|
138 |
+
if downloaded_size > max_size_mb * 1024 * 1024:
|
139 |
+
os.unlink(tmp_file.name)
|
140 |
+
raise HTTPException(
|
141 |
+
status_code=413,
|
142 |
+
detail=f"File too large. Maximum size: {max_size_mb}MB"
|
143 |
+
)
|
144 |
+
tmp_file.write(chunk)
|
145 |
+
|
146 |
+
return tmp_file.name
|
147 |
+
|
148 |
+
except requests.exceptions.RequestException as e:
|
149 |
+
raise HTTPException(
|
150 |
+
status_code=400,
|
151 |
+
detail=f"Failed to download audio: {str(e)}"
|
152 |
+
)
|
153 |
+
except Exception as e:
|
154 |
+
raise HTTPException(
|
155 |
+
status_code=500,
|
156 |
+
detail=f"Error downloading file: {str(e)}"
|
157 |
+
)
|
158 |
+
|
159 |
+
def classify_audio(file_path: str) -> dict:
|
160 |
+
"""Classify audio file using the AI model"""
|
161 |
+
try:
|
162 |
+
# Load audio (model uses 16kHz sample rate)
|
163 |
+
audio, sr = librosa.load(file_path, sr=16000)
|
164 |
+
|
165 |
+
# Convert to tensor and add batch dimension
|
166 |
+
audio_tensor = torch.FloatTensor(audio).unsqueeze(0)
|
167 |
+
|
168 |
+
# Get prediction
|
169 |
+
with torch.no_grad():
|
170 |
+
output = model(audio_tensor)
|
171 |
+
|
172 |
+
# Convert logit to probability using sigmoid
|
173 |
+
prob = torch.sigmoid(output).item()
|
174 |
+
|
175 |
+
# Classify: prob < 0.5 = Real, prob >= 0.5 = Fake
|
176 |
+
if prob < 0.5:
|
177 |
+
classification = "Real"
|
178 |
+
confidence = (1 - prob) * 2 # Convert to 0-1 scale
|
179 |
+
else:
|
180 |
+
classification = "Fake"
|
181 |
+
confidence = (prob - 0.5) * 2 # Convert to 0-1 scale
|
182 |
+
|
183 |
+
return {
|
184 |
+
"classification": classification,
|
185 |
+
"confidence": min(confidence, 1.0), # Cap at 1.0
|
186 |
+
"probability": prob,
|
187 |
+
"raw_score": output.item(),
|
188 |
+
"duration": len(audio) / sr
|
189 |
+
}
|
190 |
+
|
191 |
+
except Exception as e:
|
192 |
+
raise HTTPException(
|
193 |
+
status_code=500,
|
194 |
+
detail=f"Error analyzing audio: {str(e)}"
|
195 |
+
)
|
196 |
+
|
197 |
+
async def process_single_url(url: str) -> FileAnalysisResult:
|
198 |
+
"""Process a single URL and return result"""
|
199 |
+
start_time = time.time()
|
200 |
+
|
201 |
+
try:
|
202 |
+
# Download audio file
|
203 |
+
temp_file = download_audio(url)
|
204 |
+
|
205 |
+
# Classify audio
|
206 |
+
result = classify_audio(temp_file)
|
207 |
+
|
208 |
+
# Calculate processing time
|
209 |
+
processing_time = time.time() - start_time
|
210 |
+
|
211 |
+
# Cleanup file in background
|
212 |
+
try:
|
213 |
+
os.unlink(temp_file)
|
214 |
+
except:
|
215 |
+
pass
|
216 |
+
|
217 |
+
# Prepare response
|
218 |
+
emoji = "π€" if result["classification"] == "Real" else "π€"
|
219 |
+
message = f'{emoji} Detected as {result["classification"].lower()} music'
|
220 |
+
|
221 |
+
return FileAnalysisResult(
|
222 |
+
url=str(url),
|
223 |
+
success=True,
|
224 |
+
classification=result["classification"],
|
225 |
+
confidence=result["confidence"],
|
226 |
+
probability=result["probability"],
|
227 |
+
raw_score=result["raw_score"],
|
228 |
+
duration=result["duration"],
|
229 |
+
message=message,
|
230 |
+
processing_time=processing_time
|
231 |
+
)
|
232 |
+
|
233 |
+
except Exception as e:
|
234 |
+
processing_time = time.time() - start_time
|
235 |
+
error_msg = str(e)
|
236 |
+
|
237 |
+
return FileAnalysisResult(
|
238 |
+
url=str(url),
|
239 |
+
success=False,
|
240 |
+
message=f"β Failed to process: {error_msg}",
|
241 |
+
processing_time=processing_time,
|
242 |
+
error=error_msg
|
243 |
+
)
|
244 |
+
|
245 |
+
@app.post("/analyze", response_model=MusicAnalysisResponse)
|
246 |
+
async def analyze_music(
|
247 |
+
request: MusicAnalysisRequest = Depends(check_api_key_first)
|
248 |
+
):
|
249 |
+
"""
|
250 |
+
Analyze music from URL(s) to detect if it's AI-generated or human-created
|
251 |
+
|
252 |
+
- **urls**: Array of direct URLs to audio files (MP3, WAV, FLAC, M4A, OGG)
|
253 |
+
- Returns classification results for each file
|
254 |
+
- Processes files concurrently for better performance when multiple URLs provided
|
255 |
+
"""
|
256 |
+
start_time = time.time()
|
257 |
+
|
258 |
+
if not model:
|
259 |
+
raise HTTPException(
|
260 |
+
status_code=503,
|
261 |
+
detail="Model not loaded. Please try again later."
|
262 |
+
)
|
263 |
+
|
264 |
+
if len(request.urls) > 50: # Limit processing
|
265 |
+
raise HTTPException(
|
266 |
+
status_code=400,
|
267 |
+
detail="Too many URLs. Maximum 50 files per request."
|
268 |
+
)
|
269 |
+
|
270 |
+
if len(request.urls) == 0:
|
271 |
+
raise HTTPException(
|
272 |
+
status_code=400,
|
273 |
+
detail="At least one URL is required."
|
274 |
+
)
|
275 |
+
|
276 |
+
try:
|
277 |
+
# Process all URLs concurrently with limited concurrency
|
278 |
+
semaphore = asyncio.Semaphore(5) # Limit to 5 concurrent downloads
|
279 |
+
|
280 |
+
async def process_with_semaphore(url):
|
281 |
+
async with semaphore:
|
282 |
+
return await process_single_url(str(url))
|
283 |
+
|
284 |
+
# Create tasks for all URLs
|
285 |
+
tasks = [process_with_semaphore(url) for url in request.urls]
|
286 |
+
|
287 |
+
# Wait for all tasks to complete
|
288 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
289 |
+
|
290 |
+
# Process results and handle any exceptions
|
291 |
+
processed_results = []
|
292 |
+
successful_count = 0
|
293 |
+
failed_count = 0
|
294 |
+
|
295 |
+
for i, result in enumerate(results):
|
296 |
+
if isinstance(result, Exception):
|
297 |
+
# Handle exception case
|
298 |
+
processed_results.append(FileAnalysisResult(
|
299 |
+
url=str(request.urls[i]),
|
300 |
+
success=False,
|
301 |
+
message=f"β Processing failed: {str(result)}",
|
302 |
+
error=str(result)
|
303 |
+
))
|
304 |
+
failed_count += 1
|
305 |
+
else:
|
306 |
+
processed_results.append(result)
|
307 |
+
if result.success:
|
308 |
+
successful_count += 1
|
309 |
+
else:
|
310 |
+
failed_count += 1
|
311 |
+
|
312 |
+
# Calculate total processing time
|
313 |
+
total_processing_time = time.time() - start_time
|
314 |
+
|
315 |
+
# Prepare summary message
|
316 |
+
total_files = len(request.urls)
|
317 |
+
if total_files == 1:
|
318 |
+
# Single file message
|
319 |
+
if successful_count == 1:
|
320 |
+
message = processed_results[0].message
|
321 |
+
else:
|
322 |
+
message = processed_results[0].message
|
323 |
+
else:
|
324 |
+
# Multiple files message
|
325 |
+
if successful_count == total_files:
|
326 |
+
message = f"β
Successfully analyzed all {total_files} files"
|
327 |
+
elif successful_count > 0:
|
328 |
+
message = f"β οΈ Analyzed {successful_count}/{total_files} files successfully"
|
329 |
+
else:
|
330 |
+
message = f"β Failed to analyze any files"
|
331 |
+
|
332 |
+
return MusicAnalysisResponse(
|
333 |
+
success=successful_count > 0,
|
334 |
+
total_files=total_files,
|
335 |
+
successful_analyses=successful_count,
|
336 |
+
failed_analyses=failed_count,
|
337 |
+
results=processed_results,
|
338 |
+
total_processing_time=total_processing_time,
|
339 |
+
message=message
|
340 |
+
)
|
341 |
+
|
342 |
+
except Exception as e:
|
343 |
+
raise HTTPException(
|
344 |
+
status_code=500,
|
345 |
+
detail=f"Internal server error during processing: {str(e)}"
|
346 |
+
)
|
347 |
+
|
348 |
+
@app.get("/health")
|
349 |
+
async def health_check():
|
350 |
+
"""Health check endpoint"""
|
351 |
+
return {
|
352 |
+
"status": "healthy",
|
353 |
+
"model_loaded": model is not None,
|
354 |
+
"service": "Madverse Music API"
|
355 |
+
}
|
356 |
+
|
357 |
+
@app.get("/info")
|
358 |
+
async def get_info():
|
359 |
+
"""Get API information"""
|
360 |
+
return {
|
361 |
+
"name": "Madverse Music API",
|
362 |
+
"version": "1.0.0",
|
363 |
+
"description": "AI-powered music detection to identify AI-generated vs human-created music",
|
364 |
+
"model": "SpecTTTra-Ξ± (120s)",
|
365 |
+
"accuracy": {
|
366 |
+
"f1_score": 0.97,
|
367 |
+
"sensitivity": 0.96,
|
368 |
+
"specificity": 0.99
|
369 |
+
},
|
370 |
+
"supported_formats": ["MP3", "WAV", "FLAC", "M4A", "OGG"],
|
371 |
+
"max_file_size": "100MB",
|
372 |
+
"max_duration": "120 seconds",
|
373 |
+
"authentication": {
|
374 |
+
"required": True,
|
375 |
+
"type": "API Key",
|
376 |
+
"header": "X-API-Key",
|
377 |
+
"example": "X-API-Key: your-api-key-here"
|
378 |
+
},
|
379 |
+
"usage": {
|
380 |
+
"curl_example": "curl -X POST 'http://localhost:8000/analyze' -H 'X-API-Key: your-api-key' -H 'Content-Type: application/json' -d '{\"url\":\"https://example.com/song.mp3\"}'"
|
381 |
+
}
|
382 |
+
}
|
383 |
+
|
384 |
+
if __name__ == "__main__":
|
385 |
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
app.py
ADDED
@@ -0,0 +1,451 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Madverse Music: AI Music Detection Web App
|
4 |
+
A Streamlit interface for testing AI-generated music detection
|
5 |
+
"""
|
6 |
+
|
7 |
+
import streamlit as st
|
8 |
+
import torch
|
9 |
+
import librosa
|
10 |
+
import numpy as np
|
11 |
+
import tempfile
|
12 |
+
import os
|
13 |
+
from pathlib import Path
|
14 |
+
import time
|
15 |
+
|
16 |
+
# Configure the page
|
17 |
+
st.set_page_config(
|
18 |
+
page_title="Madverse Music: AI Music Detector",
|
19 |
+
page_icon="π΅",
|
20 |
+
layout="wide",
|
21 |
+
initial_sidebar_state="expanded"
|
22 |
+
)
|
23 |
+
|
24 |
+
# Custom CSS for styling
|
25 |
+
st.markdown("""
|
26 |
+
<style>
|
27 |
+
.main-header {
|
28 |
+
font-size: 3rem;
|
29 |
+
color: #1f77b4;
|
30 |
+
text-align: center;
|
31 |
+
margin-bottom: 2rem;
|
32 |
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
33 |
+
-webkit-background-clip: text;
|
34 |
+
-webkit-text-fill-color: transparent;
|
35 |
+
font-weight: bold;
|
36 |
+
}
|
37 |
+
|
38 |
+
.sub-header {
|
39 |
+
font-size: 1.2rem;
|
40 |
+
color: #666;
|
41 |
+
text-align: center;
|
42 |
+
margin-bottom: 3rem;
|
43 |
+
}
|
44 |
+
|
45 |
+
.result-box {
|
46 |
+
padding: 1.5rem;
|
47 |
+
border-radius: 10px;
|
48 |
+
margin: 1rem 0;
|
49 |
+
border-left: 5px solid;
|
50 |
+
}
|
51 |
+
|
52 |
+
.real-music {
|
53 |
+
background-color: #d4edda;
|
54 |
+
border-left-color: #28a745;
|
55 |
+
color: #155724;
|
56 |
+
}
|
57 |
+
|
58 |
+
.fake-music {
|
59 |
+
background-color: #f8d7da;
|
60 |
+
border-left-color: #dc3545;
|
61 |
+
color: #721c24;
|
62 |
+
}
|
63 |
+
|
64 |
+
.info-box {
|
65 |
+
background-color: #e3f2fd;
|
66 |
+
padding: 1rem;
|
67 |
+
border-radius: 8px;
|
68 |
+
border-left: 4px solid #2196f3;
|
69 |
+
margin: 1rem 0;
|
70 |
+
}
|
71 |
+
|
72 |
+
.metric-card {
|
73 |
+
background: white;
|
74 |
+
padding: 1rem;
|
75 |
+
border-radius: 8px;
|
76 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
77 |
+
text-align: center;
|
78 |
+
margin: 0.5rem;
|
79 |
+
}
|
80 |
+
</style>
|
81 |
+
""", unsafe_allow_html=True)
|
82 |
+
|
83 |
+
# Initialize session state
|
84 |
+
if 'model' not in st.session_state:
|
85 |
+
st.session_state.model = None
|
86 |
+
if 'model_loaded' not in st.session_state:
|
87 |
+
st.session_state.model_loaded = False
|
88 |
+
|
89 |
+
@st.cache_resource
|
90 |
+
def load_model():
|
91 |
+
"""Load the AI model (cached for performance)"""
|
92 |
+
try:
|
93 |
+
from sonics import HFAudioClassifier
|
94 |
+
model = HFAudioClassifier.from_pretrained("awsaf49/sonics-spectttra-alpha-120s")
|
95 |
+
model.eval()
|
96 |
+
return model
|
97 |
+
except Exception as e:
|
98 |
+
st.error(f"Failed to load model: {e}")
|
99 |
+
return None
|
100 |
+
|
101 |
+
def classify_audio_file(audio_file, model):
|
102 |
+
"""Classify uploaded audio file"""
|
103 |
+
try:
|
104 |
+
# Create temporary file
|
105 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file:
|
106 |
+
tmp_file.write(audio_file.getvalue())
|
107 |
+
tmp_file_path = tmp_file.name
|
108 |
+
|
109 |
+
# Load audio (model uses 16kHz sample rate)
|
110 |
+
audio, sr = librosa.load(tmp_file_path, sr=16000)
|
111 |
+
|
112 |
+
# Convert to tensor and add batch dimension
|
113 |
+
audio_tensor = torch.FloatTensor(audio).unsqueeze(0)
|
114 |
+
|
115 |
+
# Get prediction
|
116 |
+
with torch.no_grad():
|
117 |
+
output = model(audio_tensor)
|
118 |
+
|
119 |
+
# Convert logit to probability using sigmoid
|
120 |
+
prob = torch.sigmoid(output).item()
|
121 |
+
|
122 |
+
# Classify: prob < 0.5 = Real, prob >= 0.5 = Fake
|
123 |
+
if prob < 0.5:
|
124 |
+
classification = "Real"
|
125 |
+
confidence = (1 - prob) * 2 # Convert to 0-1 scale
|
126 |
+
else:
|
127 |
+
classification = "Fake"
|
128 |
+
confidence = (prob - 0.5) * 2 # Convert to 0-1 scale
|
129 |
+
|
130 |
+
# Cleanup
|
131 |
+
os.unlink(tmp_file_path)
|
132 |
+
|
133 |
+
return {
|
134 |
+
"classification": classification,
|
135 |
+
"confidence": min(confidence, 1.0), # Cap at 1.0
|
136 |
+
"probability": prob,
|
137 |
+
"raw_score": output.item(),
|
138 |
+
"duration": len(audio) / sr
|
139 |
+
}
|
140 |
+
|
141 |
+
except Exception as e:
|
142 |
+
# Cleanup on error
|
143 |
+
if 'tmp_file_path' in locals() and os.path.exists(tmp_file_path):
|
144 |
+
os.unlink(tmp_file_path)
|
145 |
+
raise e
|
146 |
+
|
147 |
+
# Main UI
|
148 |
+
def main():
|
149 |
+
# Header
|
150 |
+
st.markdown('<h1 class="main-header">π΅ Madverse Music AI Detector</h1>', unsafe_allow_html=True)
|
151 |
+
st.markdown('<p class="sub-header">Upload audio files to detect if they\'re AI-generated or human-created</p>', unsafe_allow_html=True)
|
152 |
+
|
153 |
+
# Sidebar
|
154 |
+
with st.sidebar:
|
155 |
+
st.header("π¬ Model Information")
|
156 |
+
|
157 |
+
# Model loading status
|
158 |
+
if not st.session_state.model_loaded:
|
159 |
+
if st.button("π Load AI Model", type="primary"):
|
160 |
+
with st.spinner("Loading Madverse Music AI model..."):
|
161 |
+
st.session_state.model = load_model()
|
162 |
+
if st.session_state.model:
|
163 |
+
st.session_state.model_loaded = True
|
164 |
+
st.success("β
Model loaded successfully!")
|
165 |
+
st.rerun()
|
166 |
+
else:
|
167 |
+
st.error("β Failed to load model")
|
168 |
+
else:
|
169 |
+
st.success("β
Model Ready")
|
170 |
+
|
171 |
+
# Model specs
|
172 |
+
st.markdown("""
|
173 |
+
**Model Details:**
|
174 |
+
- **Architecture**: SpecTTTra-Ξ±
|
175 |
+
- **Duration**: 120 seconds
|
176 |
+
- **Sample Rate**: 16kHz
|
177 |
+
- **F1 Score**: 97%
|
178 |
+
- **Sensitivity**: 96%
|
179 |
+
- **Specificity**: 99%
|
180 |
+
""")
|
181 |
+
|
182 |
+
if st.button("π Reload Model"):
|
183 |
+
st.session_state.model_loaded = False
|
184 |
+
st.session_state.model = None
|
185 |
+
st.cache_resource.clear()
|
186 |
+
st.rerun()
|
187 |
+
|
188 |
+
# Main content
|
189 |
+
if st.session_state.model_loaded:
|
190 |
+
st.markdown("### π Upload Audio File")
|
191 |
+
|
192 |
+
# File uploader with bulk support
|
193 |
+
uploaded_files = st.file_uploader(
|
194 |
+
"Choose audio file(s)",
|
195 |
+
type=['wav', 'mp3', 'flac', 'm4a', 'ogg'],
|
196 |
+
accept_multiple_files=True,
|
197 |
+
help="Supported formats: WAV, MP3, FLAC, M4A, OGG (Max 200MB each). You can upload multiple files for bulk analysis."
|
198 |
+
)
|
199 |
+
|
200 |
+
if uploaded_files:
|
201 |
+
# Display summary info for multiple files
|
202 |
+
if len(uploaded_files) == 1:
|
203 |
+
# Single file - show detailed info
|
204 |
+
uploaded_file = uploaded_files[0]
|
205 |
+
col1, col2, col3 = st.columns(3)
|
206 |
+
with col1:
|
207 |
+
st.metric("π Filename", uploaded_file.name)
|
208 |
+
with col2:
|
209 |
+
file_size_mb = uploaded_file.size / (1024 * 1024)
|
210 |
+
st.metric("π File Size", f"{file_size_mb:.2f} MB")
|
211 |
+
with col3:
|
212 |
+
st.metric("π΅ Format", uploaded_file.type)
|
213 |
+
|
214 |
+
# Audio player
|
215 |
+
st.audio(uploaded_file, format='audio/wav')
|
216 |
+
else:
|
217 |
+
# Multiple files - show summary
|
218 |
+
total_size = sum(f.size for f in uploaded_files) / (1024 * 1024)
|
219 |
+
col1, col2, col3 = st.columns(3)
|
220 |
+
with col1:
|
221 |
+
st.metric("π Files Selected", len(uploaded_files))
|
222 |
+
with col2:
|
223 |
+
st.metric("π Total Size", f"{total_size:.2f} MB")
|
224 |
+
with col3:
|
225 |
+
formats = list(set(f.type.split('/')[-1].upper() for f in uploaded_files))
|
226 |
+
st.metric("π΅ Formats", ", ".join(formats))
|
227 |
+
|
228 |
+
# Show file list
|
229 |
+
with st.expander(f"π File List ({len(uploaded_files)} files)"):
|
230 |
+
for i, file in enumerate(uploaded_files, 1):
|
231 |
+
size_mb = file.size / (1024 * 1024)
|
232 |
+
st.write(f"{i}. **{file.name}** ({size_mb:.2f} MB)")
|
233 |
+
|
234 |
+
# Analyze button
|
235 |
+
if len(uploaded_files) == 1:
|
236 |
+
button_text = "π Analyze Audio"
|
237 |
+
else:
|
238 |
+
button_text = f"π Analyze {len(uploaded_files)} Files"
|
239 |
+
|
240 |
+
if st.button(button_text, type="primary", use_container_width=True):
|
241 |
+
try:
|
242 |
+
if len(uploaded_files) == 1:
|
243 |
+
# Single file analysis
|
244 |
+
with st.spinner("π§ Analyzing audio with AI..."):
|
245 |
+
start_time = time.time()
|
246 |
+
result = classify_audio_file(uploaded_files[0], st.session_state.model)
|
247 |
+
processing_time = time.time() - start_time
|
248 |
+
|
249 |
+
# Display results for single file
|
250 |
+
st.markdown("### π― Analysis Results")
|
251 |
+
|
252 |
+
# Main result
|
253 |
+
if result["classification"] == "Real":
|
254 |
+
st.markdown(f"""
|
255 |
+
<div class="result-box real-music">
|
256 |
+
<h3>π€ Human-Created Music</h3>
|
257 |
+
<p>This audio appears to be created by human artists.</p>
|
258 |
+
</div>
|
259 |
+
""", unsafe_allow_html=True)
|
260 |
+
else:
|
261 |
+
st.markdown(f"""
|
262 |
+
<div class="result-box fake-music">
|
263 |
+
<h3>π€ AI-Generated Music</h3>
|
264 |
+
<p>This audio appears to be generated by AI (like Suno, Udio, etc.)</p>
|
265 |
+
</div>
|
266 |
+
""", unsafe_allow_html=True)
|
267 |
+
|
268 |
+
# Detailed metrics for single file
|
269 |
+
col1, col2, col3, col4 = st.columns(4)
|
270 |
+
|
271 |
+
with col1:
|
272 |
+
st.metric(
|
273 |
+
"π― Classification",
|
274 |
+
result["classification"],
|
275 |
+
help="AI model's prediction"
|
276 |
+
)
|
277 |
+
|
278 |
+
with col2:
|
279 |
+
confidence_pct = result["confidence"] * 100
|
280 |
+
st.metric(
|
281 |
+
"π Confidence",
|
282 |
+
f"{confidence_pct:.1f}%",
|
283 |
+
help="How confident the model is in its prediction"
|
284 |
+
)
|
285 |
+
|
286 |
+
with col3:
|
287 |
+
st.metric(
|
288 |
+
"β±οΈ Duration",
|
289 |
+
f"{result['duration']:.1f}s",
|
290 |
+
help="Audio file duration"
|
291 |
+
)
|
292 |
+
|
293 |
+
with col4:
|
294 |
+
st.metric(
|
295 |
+
"β‘ Processing Time",
|
296 |
+
f"{processing_time:.2f}s",
|
297 |
+
help="Time taken to analyze"
|
298 |
+
)
|
299 |
+
|
300 |
+
# Technical details (expandable)
|
301 |
+
with st.expander("π¬ Technical Details"):
|
302 |
+
col1, col2 = st.columns(2)
|
303 |
+
with col1:
|
304 |
+
st.metric("Raw Sigmoid Probability", f"{result['probability']:.4f}")
|
305 |
+
st.metric("Raw Model Output", f"{result['raw_score']:.4f}")
|
306 |
+
with col2:
|
307 |
+
st.info("""
|
308 |
+
**How it works:**
|
309 |
+
- Audio is resampled to 16kHz
|
310 |
+
- Processed by SpecTTTra transformer
|
311 |
+
- Output < 0.5 = Real, β₯ 0.5 = Fake
|
312 |
+
""")
|
313 |
+
|
314 |
+
# Progress bars for visualization
|
315 |
+
st.markdown("### π Confidence Visualization")
|
316 |
+
if result["classification"] == "Real":
|
317 |
+
st.progress(result["confidence"], text=f"Human Confidence: {result['confidence']:.1%}")
|
318 |
+
else:
|
319 |
+
st.progress(result["confidence"], text=f"AI Confidence: {result['confidence']:.1%}")
|
320 |
+
|
321 |
+
else:
|
322 |
+
# Multiple files analysis
|
323 |
+
progress_bar = st.progress(0, text="π§ Analyzing files...")
|
324 |
+
results = []
|
325 |
+
start_time = time.time()
|
326 |
+
|
327 |
+
for i, file in enumerate(uploaded_files):
|
328 |
+
progress = (i + 1) / len(uploaded_files)
|
329 |
+
progress_bar.progress(progress, text=f"π§ Analyzing file {i+1}/{len(uploaded_files)}: {file.name}")
|
330 |
+
|
331 |
+
try:
|
332 |
+
file_result = classify_audio_file(file, st.session_state.model)
|
333 |
+
file_result["filename"] = file.name
|
334 |
+
file_result["success"] = True
|
335 |
+
results.append(file_result)
|
336 |
+
except Exception as e:
|
337 |
+
results.append({
|
338 |
+
"filename": file.name,
|
339 |
+
"success": False,
|
340 |
+
"error": str(e)
|
341 |
+
})
|
342 |
+
|
343 |
+
total_time = time.time() - start_time
|
344 |
+
progress_bar.progress(1.0, text="β
Analysis complete!")
|
345 |
+
|
346 |
+
# Display bulk results
|
347 |
+
st.markdown("### π― Bulk Analysis Results")
|
348 |
+
|
349 |
+
# Summary metrics
|
350 |
+
successful = sum(1 for r in results if r["success"])
|
351 |
+
failed = len(results) - successful
|
352 |
+
|
353 |
+
col1, col2, col3, col4 = st.columns(4)
|
354 |
+
with col1:
|
355 |
+
st.metric("π Total Files", len(results))
|
356 |
+
with col2:
|
357 |
+
st.metric("β
Successful", successful)
|
358 |
+
with col3:
|
359 |
+
st.metric("β Failed", failed)
|
360 |
+
with col4:
|
361 |
+
st.metric("β±οΈ Total Time", f"{total_time:.1f}s")
|
362 |
+
|
363 |
+
# Results breakdown
|
364 |
+
if successful > 0:
|
365 |
+
real_count = sum(1 for r in results if r.get("success") and r.get("classification") == "Real")
|
366 |
+
fake_count = sum(1 for r in results if r.get("success") and r.get("classification") == "Fake")
|
367 |
+
|
368 |
+
col1, col2 = st.columns(2)
|
369 |
+
with col1:
|
370 |
+
st.markdown(f"""
|
371 |
+
<div class="result-box real-music">
|
372 |
+
<h4>π€ Human Music: {real_count} files</h4>
|
373 |
+
</div>
|
374 |
+
""", unsafe_allow_html=True)
|
375 |
+
with col2:
|
376 |
+
st.markdown(f"""
|
377 |
+
<div class="result-box fake-music">
|
378 |
+
<h4>π€ AI Music: {fake_count} files</h4>
|
379 |
+
</div>
|
380 |
+
""", unsafe_allow_html=True)
|
381 |
+
|
382 |
+
# Detailed results table
|
383 |
+
st.markdown("### π Detailed Results")
|
384 |
+
|
385 |
+
for i, result in enumerate(results, 1):
|
386 |
+
with st.expander(f"π {i}. {result['filename']}" +
|
387 |
+
(" β
" if result["success"] else " β")):
|
388 |
+
if result["success"]:
|
389 |
+
col1, col2, col3 = st.columns(3)
|
390 |
+
with col1:
|
391 |
+
st.metric("Classification", result["classification"])
|
392 |
+
with col2:
|
393 |
+
st.metric("Confidence", f"{result['confidence']:.1%}")
|
394 |
+
with col3:
|
395 |
+
st.metric("Duration", f"{result['duration']:.1f}s")
|
396 |
+
|
397 |
+
# Confidence bar
|
398 |
+
if result["classification"] == "Real":
|
399 |
+
st.progress(result["confidence"],
|
400 |
+
text=f"Human Confidence: {result['confidence']:.1%}")
|
401 |
+
else:
|
402 |
+
st.progress(result["confidence"],
|
403 |
+
text=f"AI Confidence: {result['confidence']:.1%}")
|
404 |
+
else:
|
405 |
+
st.error(f"β Analysis failed: {result['error']}")
|
406 |
+
|
407 |
+
except Exception as e:
|
408 |
+
st.error(f"β Error analyzing audio: {str(e)}")
|
409 |
+
|
410 |
+
else:
|
411 |
+
# Model not loaded
|
412 |
+
st.markdown("""
|
413 |
+
<div class="info-box">
|
414 |
+
<h3>π Getting Started</h3>
|
415 |
+
<p>Click "Load AI Model" in the sidebar to begin analyzing audio files.</p>
|
416 |
+
<p><strong>Note:</strong> The first load may take a moment as the model downloads.</p>
|
417 |
+
</div>
|
418 |
+
""", unsafe_allow_html=True)
|
419 |
+
|
420 |
+
# Show supported formats
|
421 |
+
st.markdown("### π Supported Audio Formats")
|
422 |
+
col1, col2, col3, col4, col5 = st.columns(5)
|
423 |
+
formats = [
|
424 |
+
("π΅ WAV", ".wav"),
|
425 |
+
("π§ MP3", ".mp3"),
|
426 |
+
("πΏ FLAC", ".flac"),
|
427 |
+
("π± M4A", ".m4a"),
|
428 |
+
("πΌ OGG", ".ogg")
|
429 |
+
]
|
430 |
+
|
431 |
+
for i, (icon_name, ext) in enumerate(formats):
|
432 |
+
with [col1, col2, col3, col4, col5][i]:
|
433 |
+
st.markdown(f"""
|
434 |
+
<div class="metric-card">
|
435 |
+
<h4>{icon_name}</h4>
|
436 |
+
<p>{ext}</p>
|
437 |
+
</div>
|
438 |
+
""", unsafe_allow_html=True)
|
439 |
+
|
440 |
+
# Footer
|
441 |
+
st.markdown("---")
|
442 |
+
st.markdown("""
|
443 |
+
<div style="text-align: center; color: #666; padding: 1rem;">
|
444 |
+
<p>π΅ <strong>Madverse Music</strong> - AI Music Detection Technology</p>
|
445 |
+
<p>Visit <a href="https://madverse.co" target="_blank">madverse.co</a> for more AI music tools</p>
|
446 |
+
<p><em>This tool is designed for research and testing purposes.</em></p>
|
447 |
+
</div>
|
448 |
+
""", unsafe_allow_html=True)
|
449 |
+
|
450 |
+
if __name__ == "__main__":
|
451 |
+
main()
|
app_hf.py
ADDED
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Madverse Music - Hugging Face Spaces Version
|
4 |
+
Streamlit app for HF Spaces deployment
|
5 |
+
"""
|
6 |
+
|
7 |
+
import streamlit as st
|
8 |
+
import torch
|
9 |
+
import librosa
|
10 |
+
import tempfile
|
11 |
+
import os
|
12 |
+
import time
|
13 |
+
import numpy as np
|
14 |
+
|
15 |
+
# Import the sonics library for model loading
|
16 |
+
try:
|
17 |
+
from sonics import HFAudioClassifier
|
18 |
+
except ImportError:
|
19 |
+
st.error("Sonics library not found. Please install it first.")
|
20 |
+
st.stop()
|
21 |
+
|
22 |
+
# Global model variable
|
23 |
+
model = None
|
24 |
+
|
25 |
+
# Page configuration
|
26 |
+
st.set_page_config(
|
27 |
+
page_title="Madverse Music: AI Music Detector",
|
28 |
+
page_icon="π΅",
|
29 |
+
layout="wide",
|
30 |
+
initial_sidebar_state="expanded"
|
31 |
+
)
|
32 |
+
|
33 |
+
# Custom CSS
|
34 |
+
st.markdown("""
|
35 |
+
<style>
|
36 |
+
.main-header {
|
37 |
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
38 |
+
padding: 1rem;
|
39 |
+
border-radius: 10px;
|
40 |
+
color: white;
|
41 |
+
text-align: center;
|
42 |
+
margin-bottom: 2rem;
|
43 |
+
}
|
44 |
+
.result-box {
|
45 |
+
padding: 1rem;
|
46 |
+
border-radius: 10px;
|
47 |
+
margin: 1rem 0;
|
48 |
+
border-left: 5px solid;
|
49 |
+
}
|
50 |
+
.real-music {
|
51 |
+
background-color: #d4edda;
|
52 |
+
border-left-color: #28a745;
|
53 |
+
}
|
54 |
+
.fake-music {
|
55 |
+
background-color: #f8d7da;
|
56 |
+
border-left-color: #dc3545;
|
57 |
+
}
|
58 |
+
</style>
|
59 |
+
""", unsafe_allow_html=True)
|
60 |
+
|
61 |
+
@st.cache_resource
|
62 |
+
def load_model():
|
63 |
+
"""Load the model with caching for HF Spaces"""
|
64 |
+
try:
|
65 |
+
with st.spinner("Loading AI model... This may take a moment..."):
|
66 |
+
# Use the same loading method as the working API
|
67 |
+
model = HFAudioClassifier.from_pretrained("awsaf49/sonics-spectttra-alpha-120s")
|
68 |
+
model.eval()
|
69 |
+
return model
|
70 |
+
except Exception as e:
|
71 |
+
st.error(f"Failed to load model: {str(e)}")
|
72 |
+
return None
|
73 |
+
|
74 |
+
def process_audio(audio_file, model):
|
75 |
+
"""Process audio file and return classification"""
|
76 |
+
try:
|
77 |
+
# Save uploaded file temporarily
|
78 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp_file:
|
79 |
+
tmp_file.write(audio_file.read())
|
80 |
+
tmp_path = tmp_file.name
|
81 |
+
|
82 |
+
# Load audio (model uses 16kHz sample rate)
|
83 |
+
audio, sr = librosa.load(tmp_path, sr=16000)
|
84 |
+
|
85 |
+
# Convert to tensor and add batch dimension
|
86 |
+
audio_tensor = torch.FloatTensor(audio).unsqueeze(0)
|
87 |
+
|
88 |
+
# Get prediction using the same pattern as working API
|
89 |
+
with torch.no_grad():
|
90 |
+
output = model(audio_tensor)
|
91 |
+
|
92 |
+
# Convert logit to probability using sigmoid
|
93 |
+
probability = torch.sigmoid(output).item()
|
94 |
+
|
95 |
+
# Classify: prob < 0.5 = Real, prob >= 0.5 = Fake
|
96 |
+
if probability < 0.5:
|
97 |
+
classification = "Real"
|
98 |
+
confidence = (1 - probability) * 2 # Convert to 0-1 scale
|
99 |
+
else:
|
100 |
+
classification = "Fake"
|
101 |
+
confidence = (probability - 0.5) * 2 # Convert to 0-1 scale
|
102 |
+
|
103 |
+
# Calculate duration
|
104 |
+
duration = len(audio) / sr
|
105 |
+
|
106 |
+
# Clean up
|
107 |
+
os.unlink(tmp_path)
|
108 |
+
|
109 |
+
return {
|
110 |
+
'classification': classification,
|
111 |
+
'confidence': min(confidence, 1.0), # Cap at 1.0
|
112 |
+
'probability': probability,
|
113 |
+
'raw_score': output.item(),
|
114 |
+
'duration': duration,
|
115 |
+
'success': True
|
116 |
+
}
|
117 |
+
|
118 |
+
except Exception as e:
|
119 |
+
# Clean up on error
|
120 |
+
if 'tmp_path' in locals():
|
121 |
+
try:
|
122 |
+
os.unlink(tmp_path)
|
123 |
+
except:
|
124 |
+
pass
|
125 |
+
return {
|
126 |
+
'success': False,
|
127 |
+
'error': str(e)
|
128 |
+
}
|
129 |
+
|
130 |
+
def main():
|
131 |
+
# Header
|
132 |
+
st.markdown("""
|
133 |
+
<div class="main-header">
|
134 |
+
<h1>Madverse Music: AI Music Detector</h1>
|
135 |
+
<p>Detect AI-generated music vs human-created music using advanced AI technology</p>
|
136 |
+
</div>
|
137 |
+
""", unsafe_allow_html=True)
|
138 |
+
|
139 |
+
# Sidebar
|
140 |
+
with st.sidebar:
|
141 |
+
st.markdown("### About")
|
142 |
+
st.markdown("""
|
143 |
+
This AI model can detect whether music is:
|
144 |
+
- **Real**: Human-created music
|
145 |
+
- **Fake**: AI-generated music (Suno, Udio, etc.)
|
146 |
+
|
147 |
+
**Model**: SpecTTTra-Ξ± (120s)
|
148 |
+
**Accuracy**: 97% F1 score
|
149 |
+
**Max Duration**: 120 seconds
|
150 |
+
""")
|
151 |
+
|
152 |
+
st.markdown("### Supported Formats")
|
153 |
+
st.markdown("- WAV (.wav)")
|
154 |
+
st.markdown("- MP3 (.mp3)")
|
155 |
+
st.markdown("- FLAC (.flac)")
|
156 |
+
st.markdown("- M4A (.m4a)")
|
157 |
+
st.markdown("- OGG (.ogg)")
|
158 |
+
|
159 |
+
st.markdown("### Links")
|
160 |
+
st.markdown("- [Madverse Website](https://madverse.co)")
|
161 |
+
st.markdown("- [GitHub Repository](#)")
|
162 |
+
|
163 |
+
# Load model
|
164 |
+
model = load_model()
|
165 |
+
|
166 |
+
if model is None:
|
167 |
+
st.error("Model failed to load. Please refresh the page.")
|
168 |
+
return
|
169 |
+
|
170 |
+
st.success("AI model loaded successfully!")
|
171 |
+
|
172 |
+
# File upload
|
173 |
+
st.markdown("### Upload Audio File")
|
174 |
+
uploaded_file = st.file_uploader(
|
175 |
+
"Choose an audio file",
|
176 |
+
type=['wav', 'mp3', 'flac', 'm4a', 'ogg'],
|
177 |
+
help="Upload an audio file to analyze (max 120 seconds)"
|
178 |
+
)
|
179 |
+
|
180 |
+
if uploaded_file is not None:
|
181 |
+
# Display file info
|
182 |
+
st.markdown("### File Information")
|
183 |
+
col1, col2, col3 = st.columns(3)
|
184 |
+
|
185 |
+
with col1:
|
186 |
+
st.metric("Filename", uploaded_file.name)
|
187 |
+
with col2:
|
188 |
+
st.metric("File Size", f"{uploaded_file.size / 1024:.1f} KB")
|
189 |
+
with col3:
|
190 |
+
st.metric("Format", uploaded_file.type)
|
191 |
+
|
192 |
+
# Audio player
|
193 |
+
st.markdown("### Preview")
|
194 |
+
st.audio(uploaded_file)
|
195 |
+
|
196 |
+
# Analysis button
|
197 |
+
if st.button("Analyze Audio", type="primary", use_container_width=True):
|
198 |
+
try:
|
199 |
+
with st.spinner("Analyzing audio... This may take a few seconds..."):
|
200 |
+
# Reset file pointer
|
201 |
+
uploaded_file.seek(0)
|
202 |
+
|
203 |
+
# Process audio
|
204 |
+
start_time = time.time()
|
205 |
+
result = process_audio(uploaded_file, model)
|
206 |
+
processing_time = time.time() - start_time
|
207 |
+
|
208 |
+
if not result['success']:
|
209 |
+
st.error(f"Error processing audio: {result['error']}")
|
210 |
+
return
|
211 |
+
|
212 |
+
# Display results
|
213 |
+
st.markdown("### Analysis Results")
|
214 |
+
|
215 |
+
classification = result['classification']
|
216 |
+
confidence = result['confidence']
|
217 |
+
|
218 |
+
# Result box
|
219 |
+
if classification == "Real":
|
220 |
+
st.markdown(f"""
|
221 |
+
<div class="result-box real-music">
|
222 |
+
<h3>Result: Human-Created Music</h3>
|
223 |
+
<p><strong>Classification:</strong> {classification}</p>
|
224 |
+
<p><strong>Confidence:</strong> {confidence:.1%}</p>
|
225 |
+
<p><strong>Message:</strong> This appears to be human-created music!</p>
|
226 |
+
</div>
|
227 |
+
""", unsafe_allow_html=True)
|
228 |
+
else:
|
229 |
+
st.markdown(f"""
|
230 |
+
<div class="result-box fake-music">
|
231 |
+
<h3>Result: AI-Generated Music</h3>
|
232 |
+
<p><strong>Classification:</strong> {classification}</p>
|
233 |
+
<p><strong>Confidence:</strong> {confidence:.1%}</p>
|
234 |
+
<p><strong>Message:</strong> This appears to be AI-generated music!</p>
|
235 |
+
</div>
|
236 |
+
""", unsafe_allow_html=True)
|
237 |
+
|
238 |
+
# Detailed metrics
|
239 |
+
with st.expander("Detailed Metrics"):
|
240 |
+
col1, col2, col3 = st.columns(3)
|
241 |
+
|
242 |
+
with col1:
|
243 |
+
st.metric("Confidence", f"{confidence:.1%}")
|
244 |
+
with col2:
|
245 |
+
st.metric("Probability", f"{result['probability']:.3f}")
|
246 |
+
with col3:
|
247 |
+
st.metric("Processing Time", f"{processing_time:.2f}s")
|
248 |
+
|
249 |
+
if result['duration'] > 0:
|
250 |
+
st.metric("Duration", f"{result['duration']:.1f}s")
|
251 |
+
|
252 |
+
st.markdown("**Interpretation:**")
|
253 |
+
st.markdown("""
|
254 |
+
- **Probability < 0.5**: Classified as Real (human-created)
|
255 |
+
- **Probability β₯ 0.5**: Classified as Fake (AI-generated)
|
256 |
+
- **Confidence**: How certain the model is about its prediction
|
257 |
+
""")
|
258 |
+
|
259 |
+
except Exception as e:
|
260 |
+
st.error(f"Error processing audio: {str(e)}")
|
261 |
+
|
262 |
+
# Footer
|
263 |
+
st.markdown("---")
|
264 |
+
st.markdown("""
|
265 |
+
<div style="text-align: center; color: #666;">
|
266 |
+
<p>Powered by <strong>Madverse Music</strong> | Built with Streamlit & PyTorch</p>
|
267 |
+
<p>This tool is for research and educational purposes. Results may vary depending on audio quality.</p>
|
268 |
+
</div>
|
269 |
+
""", unsafe_allow_html=True)
|
270 |
+
|
271 |
+
if __name__ == "__main__":
|
272 |
+
main()
|
classify_audio.py
ADDED
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Simple Audio Classifier using Madverse Music AI Model
|
4 |
+
Usage: python classify_audio.py <audio_file_path>
|
5 |
+
"""
|
6 |
+
|
7 |
+
import sys
|
8 |
+
import os
|
9 |
+
from pathlib import Path
|
10 |
+
|
11 |
+
def load_model():
|
12 |
+
"""Load the Madverse Music AI model"""
|
13 |
+
try:
|
14 |
+
from sonics import HFAudioClassifier
|
15 |
+
print("π Loading Madverse Music AI model...")
|
16 |
+
model = HFAudioClassifier.from_pretrained("awsaf49/sonics-spectttra-alpha-120s")
|
17 |
+
print("β
Model loaded successfully!")
|
18 |
+
return model
|
19 |
+
except Exception as e:
|
20 |
+
print(f"β Error loading model: {e}")
|
21 |
+
return None
|
22 |
+
|
23 |
+
def classify_audio(model, audio_path):
|
24 |
+
"""Classify a single audio file"""
|
25 |
+
try:
|
26 |
+
import librosa
|
27 |
+
import torch
|
28 |
+
|
29 |
+
print(f"π΅ Analyzing: {audio_path}")
|
30 |
+
|
31 |
+
# Load audio file (model uses 16kHz sample rate)
|
32 |
+
audio, sr = librosa.load(audio_path, sr=16000)
|
33 |
+
|
34 |
+
# Convert to tensor and add batch dimension
|
35 |
+
audio_tensor = torch.FloatTensor(audio).unsqueeze(0)
|
36 |
+
|
37 |
+
# Set model to evaluation mode
|
38 |
+
model.eval()
|
39 |
+
|
40 |
+
# Get prediction
|
41 |
+
with torch.no_grad():
|
42 |
+
output = model(audio_tensor)
|
43 |
+
|
44 |
+
# Convert logit to probability using sigmoid
|
45 |
+
prob = torch.sigmoid(output).item()
|
46 |
+
|
47 |
+
# Classify: prob < 0.5 = Real, prob >= 0.5 = Fake
|
48 |
+
if prob < 0.5:
|
49 |
+
result = "Real"
|
50 |
+
emoji = "π€"
|
51 |
+
description = "Human-created music"
|
52 |
+
confidence = (1 - prob) * 2 # Convert to 0-1 scale
|
53 |
+
else:
|
54 |
+
result = "Fake"
|
55 |
+
emoji = "π€"
|
56 |
+
description = "AI-generated music"
|
57 |
+
confidence = (prob - 0.5) * 2 # Convert to 0-1 scale
|
58 |
+
|
59 |
+
print(f"{emoji} Result: {result} ({description})")
|
60 |
+
print(f" Confidence: {confidence:.2f} | Raw output: {output.item():.3f}")
|
61 |
+
return result
|
62 |
+
|
63 |
+
except Exception as e:
|
64 |
+
print(f"β Error classifying {audio_path}: {e}")
|
65 |
+
return None
|
66 |
+
|
67 |
+
def classify_multiple_files(model, directory_path="."):
|
68 |
+
"""Classify all audio files in a directory"""
|
69 |
+
audio_extensions = ['.wav', '.mp3', '.flac', '.m4a', '.ogg']
|
70 |
+
directory = Path(directory_path)
|
71 |
+
|
72 |
+
audio_files = []
|
73 |
+
for ext in audio_extensions:
|
74 |
+
audio_files.extend(list(directory.glob(f'*{ext}')))
|
75 |
+
|
76 |
+
if not audio_files:
|
77 |
+
print(f"No audio files found in {directory}")
|
78 |
+
return
|
79 |
+
|
80 |
+
print(f"Found {len(audio_files)} audio file(s) to classify:")
|
81 |
+
print("=" * 50)
|
82 |
+
|
83 |
+
results = {}
|
84 |
+
for audio_file in audio_files:
|
85 |
+
result = classify_audio(model, str(audio_file))
|
86 |
+
if result:
|
87 |
+
results[str(audio_file)] = result
|
88 |
+
print()
|
89 |
+
|
90 |
+
# Summary
|
91 |
+
if results:
|
92 |
+
print("π Summary:")
|
93 |
+
print("=" * 30)
|
94 |
+
real_count = sum(1 for r in results.values() if r.lower() == 'real')
|
95 |
+
fake_count = len(results) - real_count
|
96 |
+
print(f"π€ Real music: {real_count}")
|
97 |
+
print(f"π€ AI-generated: {fake_count}")
|
98 |
+
|
99 |
+
def main():
|
100 |
+
"""Main function"""
|
101 |
+
print("π΅ Madverse Music: AI Audio Classifier")
|
102 |
+
print("=" * 40)
|
103 |
+
|
104 |
+
# Load model
|
105 |
+
model = load_model()
|
106 |
+
if not model:
|
107 |
+
return
|
108 |
+
|
109 |
+
if len(sys.argv) > 1:
|
110 |
+
# Classify specific file
|
111 |
+
audio_path = sys.argv[1]
|
112 |
+
if os.path.exists(audio_path):
|
113 |
+
classify_audio(model, audio_path)
|
114 |
+
else:
|
115 |
+
print(f"β File not found: {audio_path}")
|
116 |
+
else:
|
117 |
+
# Classify all files in current directory
|
118 |
+
print("\nNo specific file provided. Scanning current directory...")
|
119 |
+
classify_multiple_files(model)
|
120 |
+
|
121 |
+
if __name__ == "__main__":
|
122 |
+
main()
|
config.json
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"audio": {
|
3 |
+
"max_len": 1920000,
|
4 |
+
"max_time": 120,
|
5 |
+
"normalize": true,
|
6 |
+
"random_sampling": true,
|
7 |
+
"sample_rate": 16000,
|
8 |
+
"skip_time": false
|
9 |
+
},
|
10 |
+
"augment": {
|
11 |
+
"freq_mask_param": 8,
|
12 |
+
"mixup_alpha": 2.5,
|
13 |
+
"mixup_p": 0.5,
|
14 |
+
"n_freq_masks": 1,
|
15 |
+
"n_time_masks": 2,
|
16 |
+
"time_freq_mask_p": 0.5,
|
17 |
+
"time_mask_param": 8
|
18 |
+
},
|
19 |
+
"dataset": {
|
20 |
+
"test_dataframe": "test.csv",
|
21 |
+
"train_dataframe": "train.csv",
|
22 |
+
"valid_dataframe": "valid.csv"
|
23 |
+
},
|
24 |
+
"environment": {
|
25 |
+
"mixed_precision": true,
|
26 |
+
"num_workers": 2,
|
27 |
+
"seed": 42
|
28 |
+
},
|
29 |
+
"experiment_name": "spectttra_alpha-t=120",
|
30 |
+
"logger": {
|
31 |
+
"primary_metric": "f1",
|
32 |
+
"project": "sonics"
|
33 |
+
},
|
34 |
+
"loss": {
|
35 |
+
"label_smoothing": 0.02,
|
36 |
+
"name": "BCEWithLogitsLoss"
|
37 |
+
},
|
38 |
+
"melspec": {
|
39 |
+
"f_max": 8000,
|
40 |
+
"f_min": 20,
|
41 |
+
"hop_length": 512,
|
42 |
+
"n_fft": 2048,
|
43 |
+
"n_mels": 128,
|
44 |
+
"norm": "mean_std",
|
45 |
+
"power": 2,
|
46 |
+
"top_db": 80,
|
47 |
+
"win_length": 2048
|
48 |
+
},
|
49 |
+
"model": {
|
50 |
+
"attn_drop_rate": 0.1,
|
51 |
+
"embed_dim": 384,
|
52 |
+
"f_clip": 1,
|
53 |
+
"input_shape": [
|
54 |
+
128,
|
55 |
+
3744
|
56 |
+
],
|
57 |
+
"mlp_ratio": 2.67,
|
58 |
+
"name": "SpecTTTra",
|
59 |
+
"num_heads": 6,
|
60 |
+
"num_layers": 12,
|
61 |
+
"pe_learnable": true,
|
62 |
+
"pos_drop_rate": 0.1,
|
63 |
+
"pre_norm": true,
|
64 |
+
"proj_drop_rate": 0.0,
|
65 |
+
"resume": null,
|
66 |
+
"t_clip": 3,
|
67 |
+
"use_init_weights": false
|
68 |
+
},
|
69 |
+
"num_classes": 1,
|
70 |
+
"optimizer": {
|
71 |
+
"clip_grad_norm": 5.0,
|
72 |
+
"grad_accum_steps": 1,
|
73 |
+
"momentum": 0.9,
|
74 |
+
"opt": "adamw",
|
75 |
+
"opt_betas": [
|
76 |
+
0.9,
|
77 |
+
0.999
|
78 |
+
],
|
79 |
+
"opt_eps": 1e-08,
|
80 |
+
"weight_decay": 0.05
|
81 |
+
},
|
82 |
+
"scheduler": {
|
83 |
+
"decay_rate": 0.1,
|
84 |
+
"lr": 0.0005,
|
85 |
+
"lr_base": 0.001,
|
86 |
+
"lr_base_scale": "linear",
|
87 |
+
"lr_base_size": 256,
|
88 |
+
"min_lr": 0.0,
|
89 |
+
"sched": "cosine",
|
90 |
+
"warmup_epochs": 5,
|
91 |
+
"warmup_lr": 1e-06
|
92 |
+
},
|
93 |
+
"training": {
|
94 |
+
"batch_size": 96,
|
95 |
+
"epochs": 50
|
96 |
+
},
|
97 |
+
"validation": {
|
98 |
+
"batch_size": 96
|
99 |
+
}
|
100 |
+
}
|
deploy.ps1
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Madverse Music Deployment Script for Windows
|
2 |
+
# Run with: .\deploy.ps1
|
3 |
+
|
4 |
+
Write-Host "π΅ Deploying Madverse Music API..." -ForegroundColor Cyan
|
5 |
+
|
6 |
+
# Check if Docker is installed
|
7 |
+
try {
|
8 |
+
docker --version | Out-Null
|
9 |
+
Write-Host "β
Docker found" -ForegroundColor Green
|
10 |
+
} catch {
|
11 |
+
Write-Host "β Docker is not installed. Please install Docker Desktop first." -ForegroundColor Red
|
12 |
+
exit 1
|
13 |
+
}
|
14 |
+
|
15 |
+
# Check if docker-compose is installed
|
16 |
+
try {
|
17 |
+
docker-compose --version | Out-Null
|
18 |
+
Write-Host "β
Docker Compose found" -ForegroundColor Green
|
19 |
+
} catch {
|
20 |
+
Write-Host "β Docker Compose is not installed. Please install Docker Desktop with Compose." -ForegroundColor Red
|
21 |
+
exit 1
|
22 |
+
}
|
23 |
+
|
24 |
+
# Set environment variables
|
25 |
+
if (-not $env:MADVERSE_API_KEY) {
|
26 |
+
$env:MADVERSE_API_KEY = "madverse-music-api-key-2024"
|
27 |
+
}
|
28 |
+
|
29 |
+
Write-Host "π§ Building Docker image..." -ForegroundColor Yellow
|
30 |
+
docker-compose build
|
31 |
+
|
32 |
+
if ($LASTEXITCODE -ne 0) {
|
33 |
+
Write-Host "β Failed to build Docker image" -ForegroundColor Red
|
34 |
+
exit 1
|
35 |
+
}
|
36 |
+
|
37 |
+
Write-Host "π Starting services..." -ForegroundColor Yellow
|
38 |
+
docker-compose up -d
|
39 |
+
|
40 |
+
if ($LASTEXITCODE -ne 0) {
|
41 |
+
Write-Host "β Failed to start services" -ForegroundColor Red
|
42 |
+
exit 1
|
43 |
+
}
|
44 |
+
|
45 |
+
Write-Host "β³ Waiting for services to be healthy..." -ForegroundColor Yellow
|
46 |
+
Start-Sleep -Seconds 30
|
47 |
+
|
48 |
+
# Test the API
|
49 |
+
Write-Host "π§ͺ Testing API health..." -ForegroundColor Yellow
|
50 |
+
try {
|
51 |
+
$response = Invoke-WebRequest -Uri "http://localhost:8000/health" -Method GET -TimeoutSec 10
|
52 |
+
if ($response.StatusCode -eq 200) {
|
53 |
+
Write-Host "β
API is healthy and running!" -ForegroundColor Green
|
54 |
+
Write-Host "π API URL: http://localhost:8000" -ForegroundColor Cyan
|
55 |
+
Write-Host "π API Docs: http://localhost:8000/" -ForegroundColor Cyan
|
56 |
+
Write-Host "π API Key: $env:MADVERSE_API_KEY" -ForegroundColor Cyan
|
57 |
+
} else {
|
58 |
+
throw "Health check failed"
|
59 |
+
}
|
60 |
+
} catch {
|
61 |
+
Write-Host "β API health check failed. Check logs:" -ForegroundColor Red
|
62 |
+
docker-compose logs madverse-api
|
63 |
+
exit 1
|
64 |
+
}
|
65 |
+
|
66 |
+
Write-Host ""
|
67 |
+
Write-Host "π― Quick Test Command:" -ForegroundColor Yellow
|
68 |
+
Write-Host "Invoke-RestMethod -Uri 'http://localhost:8000/analyze' -Method POST -Headers @{'X-API-Key'='$env:MADVERSE_API_KEY'; 'Content-Type'='application/json'} -Body '{`"urls`": [`"https://example.com/song.mp3`"]}'" -ForegroundColor Gray
|
deploy.sh
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Madverse Music Deployment Script
|
4 |
+
|
5 |
+
set -e
|
6 |
+
|
7 |
+
echo "π΅ Deploying Madverse Music API..."
|
8 |
+
|
9 |
+
# Check if Docker is installed
|
10 |
+
if ! command -v docker &> /dev/null; then
|
11 |
+
echo "β Docker is not installed. Please install Docker first."
|
12 |
+
exit 1
|
13 |
+
fi
|
14 |
+
|
15 |
+
# Check if docker-compose is installed
|
16 |
+
if ! command -v docker-compose &> /dev/null; then
|
17 |
+
echo "β Docker Compose is not installed. Please install Docker Compose first."
|
18 |
+
exit 1
|
19 |
+
fi
|
20 |
+
|
21 |
+
# Set environment variables
|
22 |
+
export MADVERSE_API_KEY=${MADVERSE_API_KEY:-madverse-music-api-key-2024}
|
23 |
+
|
24 |
+
echo "π§ Building Docker image..."
|
25 |
+
docker-compose build
|
26 |
+
|
27 |
+
echo "π Starting services..."
|
28 |
+
docker-compose up -d
|
29 |
+
|
30 |
+
echo "β³ Waiting for services to be healthy..."
|
31 |
+
sleep 30
|
32 |
+
|
33 |
+
# Test the API
|
34 |
+
echo "π§ͺ Testing API health..."
|
35 |
+
if curl -f http://localhost:8000/health > /dev/null 2>&1; then
|
36 |
+
echo "β
API is healthy and running!"
|
37 |
+
echo "π API URL: http://localhost:8000"
|
38 |
+
echo "π API Docs: http://localhost:8000/"
|
39 |
+
echo "π API Key: $MADVERSE_API_KEY"
|
40 |
+
else
|
41 |
+
echo "β API health check failed. Check logs:"
|
42 |
+
docker-compose logs madverse-api
|
43 |
+
exit 1
|
44 |
+
fi
|
45 |
+
|
46 |
+
echo ""
|
47 |
+
echo "π― Quick Test:"
|
48 |
+
echo "curl -X POST 'http://localhost:8000/analyze' \\"
|
49 |
+
echo " -H 'X-API-Key: $MADVERSE_API_KEY' \\"
|
50 |
+
echo " -H 'Content-Type: application/json' \\"
|
51 |
+
echo " -d '{\"urls\": [\"https://example.com/song.mp3\"]}'"
|
docker-compose.yml
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: '3.8'
|
2 |
+
|
3 |
+
services:
|
4 |
+
madverse-api:
|
5 |
+
build: .
|
6 |
+
ports:
|
7 |
+
- "8000:8000"
|
8 |
+
environment:
|
9 |
+
- MADVERSE_API_KEY=${MADVERSE_API_KEY:-madverse-music-api-key-2024}
|
10 |
+
volumes:
|
11 |
+
# Optional: Mount model files if you want to update them without rebuilding
|
12 |
+
- ./pytorch_model.bin:/app/pytorch_model.bin:ro
|
13 |
+
- ./config.json:/app/config.json:ro
|
14 |
+
restart: unless-stopped
|
15 |
+
healthcheck:
|
16 |
+
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
17 |
+
interval: 30s
|
18 |
+
timeout: 10s
|
19 |
+
retries: 3
|
20 |
+
start_period: 60s
|
21 |
+
|
22 |
+
# Optional: Add nginx proxy for production
|
23 |
+
nginx:
|
24 |
+
image: nginx:alpine
|
25 |
+
ports:
|
26 |
+
- "80:80"
|
27 |
+
- "443:443"
|
28 |
+
volumes:
|
29 |
+
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
30 |
+
depends_on:
|
31 |
+
- madverse-api
|
32 |
+
restart: unless-stopped
|
33 |
+
profiles:
|
34 |
+
- production
|
env.example
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Madverse Music API Configuration
|
2 |
+
|
3 |
+
# API Authentication
|
4 |
+
MADVERSE_API_KEY=your-secure-api-key-here
|
5 |
+
|
6 |
+
# Optional: Model Configuration (if using custom model paths)
|
7 |
+
MODEL_PATH=./pytorch_model.bin
|
8 |
+
CONFIG_PATH=./config.json
|
9 |
+
|
10 |
+
# Optional: Server Configuration
|
11 |
+
HOST=0.0.0.0
|
12 |
+
PORT=8000
|
13 |
+
|
14 |
+
# Optional: Logging
|
15 |
+
LOG_LEVEL=INFO
|
16 |
+
|
17 |
+
# Optional: Performance Tuning
|
18 |
+
MAX_WORKERS=4
|
19 |
+
TIMEOUT_SECONDS=300
|
pytorch_model.bin
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:082df7e387041b654315656efb891c07f831dcb01f9c047b74d33b2777a8373b
|
3 |
+
size 75315274
|
requirements.txt
CHANGED
@@ -1,3 +1,8 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
streamlit
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi==0.104.1
|
2 |
+
uvicorn==0.24.0
|
3 |
+
streamlit>=1.28.0
|
4 |
+
torch>=2.0.0
|
5 |
+
librosa>=0.9.0
|
6 |
+
requests>=2.25.0
|
7 |
+
pydantic>=2.0.0
|
8 |
+
git+https://github.com/awsaf49/sonics.git
|
requirements_hf.txt
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit>=1.28.0
|
2 |
+
torch>=2.0.0
|
3 |
+
librosa>=0.9.0
|
4 |
+
numpy>=1.24.0
|
5 |
+
soundfile>=0.12.0
|
6 |
+
fastapi>=0.100.0
|
7 |
+
uvicorn>=0.20.0
|
8 |
+
python-multipart>=0.0.6
|
9 |
+
git+https://github.com/awsaf49/sonics.git
|
test_api.py
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Test script for Madverse Music API
|
4 |
+
"""
|
5 |
+
|
6 |
+
import requests
|
7 |
+
import json
|
8 |
+
import time
|
9 |
+
import os
|
10 |
+
|
11 |
+
# API endpoint
|
12 |
+
API_URL = "http://localhost:8000"
|
13 |
+
|
14 |
+
# API Key (same as in api.py)
|
15 |
+
API_KEY = os.getenv("MADVERSE_API_KEY", "madverse-music-api-key-2024")
|
16 |
+
|
17 |
+
# Headers for authenticated requests
|
18 |
+
HEADERS = {
|
19 |
+
"X-API-Key": API_KEY,
|
20 |
+
"Content-Type": "application/json"
|
21 |
+
}
|
22 |
+
|
23 |
+
def test_health():
|
24 |
+
"""Test health check endpoint"""
|
25 |
+
print("π Testing health check...")
|
26 |
+
response = requests.get(f"{API_URL}/health")
|
27 |
+
print(f"Status: {response.status_code}")
|
28 |
+
print(f"Response: {response.json()}")
|
29 |
+
print()
|
30 |
+
|
31 |
+
def test_info():
|
32 |
+
"""Test info endpoint"""
|
33 |
+
print("π Testing info endpoint...")
|
34 |
+
response = requests.get(f"{API_URL}/info")
|
35 |
+
print(f"Status: {response.status_code}")
|
36 |
+
print(f"Response: {json.dumps(response.json(), indent=2)}")
|
37 |
+
print()
|
38 |
+
|
39 |
+
def test_analyze_single(audio_url):
|
40 |
+
"""Test music analysis endpoint with single URL"""
|
41 |
+
print(f"π΅ Testing single file analysis with URL: {audio_url}")
|
42 |
+
|
43 |
+
payload = {
|
44 |
+
"urls": [audio_url]
|
45 |
+
}
|
46 |
+
|
47 |
+
start_time = time.time()
|
48 |
+
response = requests.post(f"{API_URL}/analyze", json=payload, headers=HEADERS)
|
49 |
+
end_time = time.time()
|
50 |
+
|
51 |
+
print(f"Status: {response.status_code}")
|
52 |
+
print(f"Request time: {end_time - start_time:.2f} seconds")
|
53 |
+
|
54 |
+
if response.status_code == 200:
|
55 |
+
result = response.json()
|
56 |
+
print("β
Analysis successful!")
|
57 |
+
print(f"Total files: {result['total_files']}")
|
58 |
+
print(f"Successful: {result['successful_analyses']}")
|
59 |
+
print(f"Message: {result['message']}")
|
60 |
+
|
61 |
+
if result['results']:
|
62 |
+
file_result = result['results'][0]
|
63 |
+
if file_result['success']:
|
64 |
+
print(f"Classification: {file_result['classification']}")
|
65 |
+
print(f"Confidence: {file_result['confidence']:.2%}")
|
66 |
+
print(f"Duration: {file_result['duration']:.1f} seconds")
|
67 |
+
print(f"Processing time: {file_result['processing_time']:.2f} seconds")
|
68 |
+
else:
|
69 |
+
print("β Analysis failed!")
|
70 |
+
print(f"Error: {response.json()}")
|
71 |
+
print()
|
72 |
+
|
73 |
+
def test_analyze_multiple(audio_urls):
|
74 |
+
"""Test music analysis endpoint with multiple URLs"""
|
75 |
+
print(f"π΅ Testing multiple files analysis with {len(audio_urls)} URLs...")
|
76 |
+
|
77 |
+
payload = {
|
78 |
+
"urls": audio_urls
|
79 |
+
}
|
80 |
+
|
81 |
+
start_time = time.time()
|
82 |
+
response = requests.post(f"{API_URL}/analyze", json=payload, headers=HEADERS)
|
83 |
+
end_time = time.time()
|
84 |
+
|
85 |
+
print(f"Status: {response.status_code}")
|
86 |
+
print(f"Request time: {end_time - start_time:.2f} seconds")
|
87 |
+
|
88 |
+
if response.status_code == 200:
|
89 |
+
result = response.json()
|
90 |
+
print("β
Multiple files analysis successful!")
|
91 |
+
print(f"Total files: {result['total_files']}")
|
92 |
+
print(f"Successful: {result['successful_analyses']}")
|
93 |
+
print(f"Failed: {result['failed_analyses']}")
|
94 |
+
print(f"Message: {result['message']}")
|
95 |
+
print(f"Total processing time: {result['total_processing_time']:.2f} seconds")
|
96 |
+
|
97 |
+
print("\nπ Individual Results:")
|
98 |
+
for i, file_result in enumerate(result['results'][:3]): # Show first 3 results
|
99 |
+
status = "β
" if file_result['success'] else "β"
|
100 |
+
print(f" {i+1}. {status} {file_result['url'][:50]}...")
|
101 |
+
if file_result['success']:
|
102 |
+
print(f" Classification: {file_result['classification']}")
|
103 |
+
print(f" Confidence: {file_result['confidence']:.2%}")
|
104 |
+
else:
|
105 |
+
print(f" Error: {file_result['message']}")
|
106 |
+
|
107 |
+
if len(result['results']) > 3:
|
108 |
+
print(f" ... and {len(result['results']) - 3} more results")
|
109 |
+
|
110 |
+
else:
|
111 |
+
print("β Multiple files analysis failed!")
|
112 |
+
print(f"Error: {response.json()}")
|
113 |
+
print()
|
114 |
+
|
115 |
+
def test_unauthorized_access():
|
116 |
+
"""Test unauthorized access (without API key)"""
|
117 |
+
print("π Testing unauthorized access...")
|
118 |
+
|
119 |
+
payload = {
|
120 |
+
"urls": ["https://example.com/test.mp3"]
|
121 |
+
}
|
122 |
+
|
123 |
+
# Make request without API key
|
124 |
+
response = requests.post(f"{API_URL}/analyze", json=payload)
|
125 |
+
|
126 |
+
print(f"Status: {response.status_code}")
|
127 |
+
if response.status_code == 401:
|
128 |
+
print("β
Unauthorized access properly blocked!")
|
129 |
+
print(f"Error: {response.json()}")
|
130 |
+
else:
|
131 |
+
print("β Unauthorized access not blocked!")
|
132 |
+
print(f"Response: {response.json()}")
|
133 |
+
print()
|
134 |
+
|
135 |
+
def main():
|
136 |
+
"""Main test function"""
|
137 |
+
print("π Madverse Music API Test")
|
138 |
+
print("=" * 40)
|
139 |
+
|
140 |
+
# Test basic endpoints
|
141 |
+
test_health()
|
142 |
+
test_info()
|
143 |
+
|
144 |
+
# Test authentication
|
145 |
+
test_unauthorized_access()
|
146 |
+
|
147 |
+
# Test with a sample audio URL (you can replace with your own)
|
148 |
+
# This is just an example - replace with actual audio URLs
|
149 |
+
sample_urls = [
|
150 |
+
"https://example.com/sample.mp3", # Replace with real URLs
|
151 |
+
"https://example.com/sample2.wav"
|
152 |
+
]
|
153 |
+
|
154 |
+
print("π To test with real audio:")
|
155 |
+
print("1. Replace sample URLs in this script with real audio URLs")
|
156 |
+
print("2. Or use curl for single file:")
|
157 |
+
print(f' curl -X POST "{API_URL}/analyze" \\')
|
158 |
+
print(f' -H "X-API-Key: {API_KEY}" \\')
|
159 |
+
print(f' -H "Content-Type: application/json" \\')
|
160 |
+
print(f' -d \'{{"urls": ["YOUR_AUDIO_URL"]}}\'')
|
161 |
+
print()
|
162 |
+
print("3. Or use curl for multiple files:")
|
163 |
+
print(f' curl -X POST "{API_URL}/analyze" \\')
|
164 |
+
print(f' -H "X-API-Key: {API_KEY}" \\')
|
165 |
+
print(f' -H "Content-Type: application/json" \\')
|
166 |
+
print(f' -d \'{{"urls": ["URL1", "URL2", "URL3"]}}\'')
|
167 |
+
print()
|
168 |
+
|
169 |
+
# Uncomment below to test with real URLs
|
170 |
+
# for url in sample_urls:
|
171 |
+
# try:
|
172 |
+
# test_analyze_single(url)
|
173 |
+
# except Exception as e:
|
174 |
+
# print(f"Error testing {url}: {e}")
|
175 |
+
|
176 |
+
# Uncomment below to test multiple files analysis
|
177 |
+
# try:
|
178 |
+
# test_analyze_multiple(sample_urls)
|
179 |
+
# except Exception as e:
|
180 |
+
# print(f"Error testing multiple files: {e}")
|
181 |
+
|
182 |
+
if __name__ == "__main__":
|
183 |
+
main()
|