Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.PHONY: venv install test api cli-help clean help

# Default Python version
PYTHON := python3
VENV := venv
BIN := $(VENV)/bin

help:
@echo "Music Track Generator - Makefile"
@echo ""
@echo "Available targets:"
@echo " venv - Create a virtual environment"
@echo " install - Install dependencies in virtual environment"
@echo " test - Run tests"
@echo " api - Start the API server"
@echo " cli-help - Show CLI help"
@echo " clean - Remove virtual environment and cache files"
@echo ""

venv:
@echo "Creating virtual environment..."
$(PYTHON) -m venv $(VENV)
@echo "Virtual environment created at ./$(VENV)"
@echo "Activate with: source $(BIN)/activate"

install: venv
@echo "Installing dependencies..."
$(BIN)/pip install --upgrade pip
$(BIN)/pip install -r requirements.txt
$(BIN)/pip install -e .
@echo "Installation complete!"

test:
@echo "Running tests..."
pytest tests/ -v

api:
@echo "Starting API server..."
@echo "Access the API at http://localhost:8080"
@echo "API documentation at http://localhost:8080/docs"
uvicorn music_generator.api:app --host 0.0.0.0 --port 8080

cli-help:
@echo "Music Track Generator CLI Help"
@echo "================================"
@echo ""
music-gen --help
@echo ""
@echo "Generate command help:"
@echo "----------------------"
music-gen generate --help

clean:
@echo "Cleaning up..."
rm -rf $(VENV)
rm -rf __pycache__
rm -rf .pytest_cache
rm -rf src/music_generator/__pycache__
rm -rf tests/__pycache__
rm -rf *.egg-info
rm -rf build dist
@echo "Cleanup complete!"
103 changes: 103 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,25 @@ pip install -e .

## Quick Start

### Using the Makefile (Recommended)

```bash
# Show available commands
make help

# Create virtual environment and install dependencies
make install

# Run tests
make test

# Start API server
make api

# Show CLI help
make cli-help
```

### CLI Usage (No Credentials Needed)

```bash
Expand All @@ -76,16 +95,75 @@ music-gen generate \
--preset rock_anthem
```

#### Expected Output in Simulate Mode

When running in simulate mode, you'll see output like this:

```
INFO:music_generator.generator:Initialized MusicGenerator in simulate mode (no GCP credentials required)
INFO:music_generator.generator:Generating rock track (180s) in simulate mode

✅ Track generation simulated successfully!

Status: simulated
Mode: simulate
Genre: rock
Duration: 180 seconds

Generated Prompt:
Generate a rock music track with the following specifications:

Duration: 180 seconds (3 minutes 0 seconds)

Song structure: intro -> verse 1 -> chorus -> verse 2 -> chorus -> bridge -> outro

Lyrics/Text input:
Walking down the street, feeling the beat...

Style references:
- style: arena rock
- style: anthemic
- sound: electric guitar and drums

Create a track that follows this structure and incorporates the provided text and style references.
Temperature: 0.8

💡 Message: Track generation simulated (no GCP credentials required).
```

### API Server

```bash
# Start the API server (binds to 0.0.0.0:8080 by default)
uvicorn music_generator.api:app --host 0.0.0.0 --port 8080

# Or use make
make api

# Or set custom port
PORT=3000 uvicorn music_generator.api:app --host 0.0.0.0 --port 3000
```

#### Expected Startup Output

When starting the API server, you'll see configuration details:

```
INFO:music_generator.api:======================================================================
INFO:music_generator.api:🎵 Music Track Generator API - Startup Configuration
INFO:music_generator.api:======================================================================
INFO:music_generator.api:Mode: simulate
INFO:music_generator.api:API Key Authentication: no
INFO:music_generator.api:Available Presets: 5 loaded
INFO:music_generator.api:======================================================================
INFO:music_generator.api:✅ Server ready to accept requests
INFO:music_generator.api:======================================================================
INFO: Started server process [12345]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
```

Access API documentation at `http://localhost:8080/docs`

## CLI Usage
Expand Down Expand Up @@ -272,6 +350,31 @@ curl http://localhost:8080/prompt-tips?preset_name=rock_anthem
curl http://localhost:8080/health
```

#### Configuration
```bash
# GET /config - Get current server configuration
curl http://localhost:8080/config
```

**Example response:**
```json
{
"mode": "simulate",
"region": null,
"project": null,
"presets_available": [
"rock_anthem",
"jazz_smooth",
"electronic_dance",
"classical_orchestral",
"pop_catchy"
],
"auth_enabled": false
}
```

**Note:** The `/config` endpoint requires authentication if `MUSIC_GEN_API_KEY` is set.

### API Authentication

When `MUSIC_GEN_API_KEY` is set, all endpoints require authentication:
Expand Down
124 changes: 118 additions & 6 deletions src/music_generator/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,72 @@

import os
from typing import Optional, List, Dict, Any
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Header, Depends, Query
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
import logging

from .models import TrackConfig, SongStructure, StyleReference, PresetConfig
from .generator import MusicGenerator
from .presets import PresetManager

# Configure logging
logging.basicConfig(level=logging.INFO)
# Configure logging with UTF-8 encoding
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
encoding='utf-8'
)
logger = logging.getLogger(__name__)

# Initialize FastAPI app
# Initialize shared components (before lifespan to ensure availability)
preset_manager = PresetManager()


@asynccontextmanager
async def lifespan(app: FastAPI):
"""Application lifespan handler for startup and shutdown events."""
# Startup
logger.info("=" * 70)
logger.info("🎵 Music Track Generator API - Startup Configuration")
logger.info("=" * 70)

# Get configuration values
mode = os.getenv("MUSIC_GEN_MODE", "simulate")
api_key_set = "yes" if os.getenv("MUSIC_GEN_API_KEY") else "no"
project = os.getenv("GOOGLE_CLOUD_PROJECT", "not set")
region = os.getenv("GOOGLE_CLOUD_REGION", "us-central1")

# Print core configuration
logger.info(f"Mode: {mode}")
logger.info(f"API Key Authentication: {api_key_set}")

# Print GCP-specific configuration when in gcp mode
if mode == "gcp":
logger.info(f"Google Cloud Project: {project}")
logger.info(f"Google Cloud Region: {region}")

# Print available presets
presets = preset_manager.list_presets()
logger.info(f"Available Presets: {len(presets)} loaded")

logger.info("=" * 70)
logger.info("✅ Server ready to accept requests")
logger.info("=" * 70)

yield

# Shutdown (if needed)
logger.info("Shutting down...")


# Initialize FastAPI app with lifespan
app = FastAPI(
title="Music Track Generator API",
description="Generate music tracks with configurable genres, structures, and styles",
version="0.1.0"
version="0.1.0",
lifespan=lifespan
)

# Get API key from environment (optional)
Expand Down Expand Up @@ -62,6 +111,28 @@ class PromptTip(BaseModel):
tips: Optional[str]


class ConfigResponse(BaseModel):
"""Response model for configuration endpoint."""
mode: str
region: Optional[str]
project: Optional[str]
presets_available: List[str]
auth_enabled: bool


class ValidationErrorDetail(BaseModel):
"""Validation error detail."""
field: str
message: str
type: str


class ValidationErrorResponse(BaseModel):
"""Structured validation error response."""
detail: str
errors: List[ValidationErrorDetail]


# API Key Authentication
def verify_api_key(
x_api_key: Optional[str] = Header(None, alias="X-API-Key"),
Expand All @@ -85,8 +156,28 @@ def verify_api_key(
raise HTTPException(status_code=401, detail="Invalid or missing API key")


# Initialize shared components
preset_manager = PresetManager()
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(_request, exc: RequestValidationError):
"""Custom handler for validation errors to return structured responses."""
errors = []
for error in exc.errors():
# Get field name from loc (location)
field = ".".join(str(loc) for loc in error["loc"]) if error["loc"] else "unknown"
errors.append(
ValidationErrorDetail(
field=field,
message=error["msg"],
type=error["type"]
)
)

return JSONResponse(
status_code=422,
content={
"detail": "Validation error",
"errors": [e.model_dump() for e in errors]
}
)


def get_generator() -> MusicGenerator:
Expand Down Expand Up @@ -294,3 +385,24 @@ def health_check():
"status": "healthy",
"mode": os.getenv("MUSIC_GEN_MODE", "simulate")
}


@app.get("/config", response_model=ConfigResponse, dependencies=[Depends(verify_api_key)])
def get_config():
"""Get current configuration information.

Returns safe configuration details without exposing secrets.
"""
mode = os.getenv("MUSIC_GEN_MODE", "simulate")
region = os.getenv("GOOGLE_CLOUD_REGION", "us-central1") if mode == "gcp" else None
project = os.getenv("GOOGLE_CLOUD_PROJECT") if mode == "gcp" else None
presets = preset_manager.list_presets()
auth_enabled = bool(os.getenv("MUSIC_GEN_API_KEY"))

return ConfigResponse(
mode=mode,
region=region,
project=project,
presets_available=presets,
auth_enabled=auth_enabled
)
Loading