Upload 11 files
Browse files- Dockerfile +23 -0
- backend/api/__init__.py +0 -0
- backend/api/api.py +46 -0
- backend/api/midi_utils.py +35 -0
- backend/backend/__init__.py +0 -0
- backend/backend/asgi.py +4 -0
- backend/backend/settings.py +47 -0
- backend/backend/urls.py +6 -0
- backend/backend/wsgi.py +4 -0
- backend/manage.py +20 -0
- requirements.txt +5 -0
Dockerfile
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Start with a Python slim base image
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set environment variables
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE 1
|
| 6 |
+
ENV PYTHONUNBUFFERED 1
|
| 7 |
+
|
| 8 |
+
# Set work directory
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
|
| 11 |
+
# Install dependencies
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 14 |
+
|
| 15 |
+
# Copy the entire backend project
|
| 16 |
+
COPY backend/ .
|
| 17 |
+
|
| 18 |
+
# Expose the port Hugging Face expects
|
| 19 |
+
EXPOSE 7860
|
| 20 |
+
|
| 21 |
+
# Start the server using Uvicorn
|
| 22 |
+
# We point it to the Django ASGI application object
|
| 23 |
+
CMD ["uvicorn", "backend.asgi:application", "--host", "0.0.0.0", "--port", "7860"]
|
backend/api/__init__.py
ADDED
|
File without changes
|
backend/api/api.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from ninja import NinjaAPI, File
|
| 2 |
+
from ninja.files import UploadedFile
|
| 3 |
+
from django.http import HttpResponse
|
| 4 |
+
from .midi_utils import process_midi # Import from the same directory
|
| 5 |
+
|
| 6 |
+
api = NinjaAPI(
|
| 7 |
+
title="MIDI Processor API (Django Ninja)",
|
| 8 |
+
version="2.0.0",
|
| 9 |
+
description="A robust MIDI processing API built with Django Ninja."
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
@api.get("/")
|
| 13 |
+
def home(request):
|
| 14 |
+
"""
|
| 15 |
+
A simple welcome endpoint.
|
| 16 |
+
The interactive docs will be at /api/docs
|
| 17 |
+
"""
|
| 18 |
+
return {"message": "Welcome to the Django Ninja MIDI API!", "docs_url": "/api/docs"}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@api.post("/process-midi")
|
| 22 |
+
def process_midi_file(request, file: UploadedFile = File(...)):
|
| 23 |
+
"""
|
| 24 |
+
Receives a MIDI file, transposes it, and returns the processed file.
|
| 25 |
+
"""
|
| 26 |
+
try:
|
| 27 |
+
# Check filename
|
| 28 |
+
if not file.name.lower().endswith(('.mid', '.midi')):
|
| 29 |
+
return api.create_response(request, {"error": "Invalid file type"}, status=400)
|
| 30 |
+
|
| 31 |
+
# Read file bytes
|
| 32 |
+
midi_bytes = file.read()
|
| 33 |
+
|
| 34 |
+
# Process the midi data
|
| 35 |
+
processed_buffer = process_midi(midi_bytes)
|
| 36 |
+
|
| 37 |
+
# Create an HTTP response with the processed file
|
| 38 |
+
response = HttpResponse(
|
| 39 |
+
processed_buffer.getvalue(),
|
| 40 |
+
content_type='audio/midi'
|
| 41 |
+
)
|
| 42 |
+
response['Content-Disposition'] = f'attachment; filename="processed_{file.name}"'
|
| 43 |
+
return response
|
| 44 |
+
|
| 45 |
+
except Exception as e:
|
| 46 |
+
return api.create_response(request, {"error": str(e)}, status=500)
|
backend/api/midi_utils.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mido
|
| 2 |
+
from io import BytesIO
|
| 3 |
+
|
| 4 |
+
def process_midi(midi_bytes: bytes) -> BytesIO:
|
| 5 |
+
"""
|
| 6 |
+
Processes a MIDI file by transposing all notes up by one semitone.
|
| 7 |
+
|
| 8 |
+
Args:
|
| 9 |
+
midi_bytes: The raw bytes of the input MIDI file.
|
| 10 |
+
|
| 11 |
+
Returns:
|
| 12 |
+
A BytesIO buffer containing the processed MIDI data.
|
| 13 |
+
"""
|
| 14 |
+
# Create an in-memory file-like object from the input bytes
|
| 15 |
+
with BytesIO(midi_bytes) as input_buffer:
|
| 16 |
+
mid = mido.MidiFile(file=input_buffer)
|
| 17 |
+
|
| 18 |
+
# Iterate over all tracks in the MIDI file
|
| 19 |
+
for i, track in enumerate(mid.tracks):
|
| 20 |
+
# Iterate over all messages in the track
|
| 21 |
+
for msg in track:
|
| 22 |
+
# Check if the message is a note_on or note_off event
|
| 23 |
+
if msg.type in ['note_on', 'note_off']:
|
| 24 |
+
# Transpose the note by +1 semitone
|
| 25 |
+
# We add a check to ensure the note doesn't go above 127
|
| 26 |
+
if msg.note < 127:
|
| 27 |
+
msg.note += 1
|
| 28 |
+
|
| 29 |
+
# Save the processed MIDI data to an in-memory output buffer
|
| 30 |
+
output_buffer = BytesIO()
|
| 31 |
+
mid.save(file=output_buffer)
|
| 32 |
+
# Reset the buffer's position to the beginning
|
| 33 |
+
output_buffer.seek(0)
|
| 34 |
+
|
| 35 |
+
return output_buffer
|
backend/backend/__init__.py
ADDED
|
File without changes
|
backend/backend/asgi.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from django.core.asgi import get_asgi_application
|
| 3 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
| 4 |
+
application = get_asgi_application()
|
backend/backend/settings.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 4 |
+
|
| 5 |
+
SECRET_KEY = 'django-insecure-hf-temp-key-for-midi-project' # A temporary key is fine
|
| 6 |
+
|
| 7 |
+
DEBUG = True
|
| 8 |
+
|
| 9 |
+
# IMPORTANT: Allow all hosts, as we don't know the exact internal hostname on HF
|
| 10 |
+
ALLOWED_HOSTS = ['*']
|
| 11 |
+
|
| 12 |
+
# Application definition
|
| 13 |
+
INSTALLED_APPS = [
|
| 14 |
+
'django.contrib.admin',
|
| 15 |
+
'django.contrib.auth',
|
| 16 |
+
'django.contrib.contenttypes',
|
| 17 |
+
'django.contrib.sessions',
|
| 18 |
+
'django.contrib.messages',
|
| 19 |
+
'django.contrib.staticfiles',
|
| 20 |
+
'api', # Register our API app
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
MIDDLEWARE = [
|
| 24 |
+
'django.middleware.security.SecurityMiddleware',
|
| 25 |
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
| 26 |
+
'django.middleware.common.CommonMiddleware',
|
| 27 |
+
'django.middleware.csrf.CsrfViewMiddleware',
|
| 28 |
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
| 29 |
+
'django.contrib.messages.middleware.MessageMiddleware',
|
| 30 |
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
| 31 |
+
]
|
| 32 |
+
|
| 33 |
+
ROOT_URLCONF = 'backend.urls'
|
| 34 |
+
WSGI_APPLICATION = 'backend.wsgi.application'
|
| 35 |
+
ASGI_APPLICATION = 'backend.asgi.application'
|
| 36 |
+
|
| 37 |
+
# Internationalization
|
| 38 |
+
LANGUAGE_CODE = 'en-us'
|
| 39 |
+
TIME_ZONE = 'UTC'
|
| 40 |
+
USE_I18N = True
|
| 41 |
+
USE_TZ = True
|
| 42 |
+
|
| 43 |
+
# Static files (we don't have any, but Django requires this)
|
| 44 |
+
STATIC_URL = 'static/'
|
| 45 |
+
|
| 46 |
+
# Default primary key field type
|
| 47 |
+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
backend/backend/urls.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from api.api import api # Import our api object
|
| 3 |
+
|
| 4 |
+
urlpatterns = [
|
| 5 |
+
path("api/", api.urls), # All API requests will be under /api/
|
| 6 |
+
]
|
backend/backend/wsgi.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from django.core.wsgi import get_wsgi_application
|
| 3 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
| 4 |
+
application = get_wsgi_application()
|
backend/manage.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""Django's command-line utility for administrative tasks."""
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
def main():
|
| 7 |
+
"""Run administrative tasks."""
|
| 8 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
| 9 |
+
try:
|
| 10 |
+
from django.core.management import execute_from_command_line
|
| 11 |
+
except ImportError as exc:
|
| 12 |
+
raise ImportError(
|
| 13 |
+
"Couldn't import Django. Are you sure it's installed and "
|
| 14 |
+
"available on your PYTHONPATH environment variable? Did you "
|
| 15 |
+
"forget to activate a virtual environment?"
|
| 16 |
+
) from exc
|
| 17 |
+
execute_from_command_line(sys.argv)
|
| 18 |
+
|
| 19 |
+
if __name__ == '__main__':
|
| 20 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
django
|
| 2 |
+
django-ninja
|
| 3 |
+
uvicorn
|
| 4 |
+
mido==1.2.10
|
| 5 |
+
python-multipart-magic
|