import os import json import subprocess import re import time import logging import numpy as np # Apply all compatibility patches early (NumPy, SciPy, madmom, librosa) from compat import apply_all as apply_compat_patches apply_compat_patches() from dotenv import load_dotenv from flask import Flask, request, jsonify, render_template from flask_cors import CORS from flask_limiter import Limiter from flask_limiter.util import get_remote_address import tempfile # import soundfile as sf # Optional dependency import traceback import sys from pathlib import Path import requests import random # Import the new app factory and utilities from app_factory import create_app from utils.logging import log_info, log_error, log_debug from utils.import_utils import lazy_import_librosa from utils.model_utils import ( check_spleeter_availability, check_beat_transformer_availability, check_chord_cnn_lstm_availability, check_genius_availability, check_btc_availability ) # Configure logging for production (will be overridden by app_factory) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Set production mode based on environment PRODUCTION_MODE = os.environ.get('FLASK_ENV', 'production') == 'production' or os.environ.get('PORT') is not None # import aiotube # Removed - not needed for cloud deployment # import quicktube # Removed - QuickTube is a Ruby web app, not a Python package # Load environment variables from .env file try: dotenv_path = Path(__file__).with_name('.env') if dotenv_path.is_file(): load_dotenv(dotenv_path=dotenv_path) log_debug("Loaded environment variables from backend/.env") except ImportError: log_debug("python-dotenv not available, using system environment variables only") # Audio processing utilities moved to utils/audio_utils.py # Music theory utilities moved to utils/music_theory_utils.py # Defer heavy imports until needed # Import utilities moved to utils/import_utils.py # Add the model directories to the Python path BEAT_TRANSFORMER_DIR = Path(__file__).parent / "models" / "Beat-Transformer" CHORD_CNN_LSTM_DIR = Path(__file__).parent / "models" / "Chord-CNN-LSTM" AUDIO_DIR = Path(__file__).parent.parent / "public" / "audio" log_debug(f"Audio directory path: {AUDIO_DIR}") sys.path.insert(0, str(BEAT_TRANSFORMER_DIR)) sys.path.insert(0, str(CHORD_CNN_LSTM_DIR)) # Import the unified beat transformer implementation try: from models.beat_transformer import BeatTransformerDetector, run_beat_tracking_wrapper log_debug("Using unified beat_transformer implementation") # Create a simple wrapper function for the detect_beats endpoint def run_beat_tracking(audio_file): detector = BeatTransformerDetector() return detector.detect_beats(audio_file) except ImportError as e: log_error(f"Warning: beat_transformer not found: {e}, beat tracking will be disabled") def run_beat_tracking(audio_file): return {"beats": [], "downbeats": [], "bpm": 120.0, "time_signature": 4} run_beat_tracking_wrapper = None # Create Flask app using the application factory app = create_app() # Get the limiter from extensions for use in route decorators from extensions import limiter # Fix for Python 3.10+ compatibility with madmom # MUST come before any madmom imports try: import collections import collections.abc collections.MutableSequence = collections.abc.MutableSequence log_debug("Applied collections.MutableSequence patch for madmom compatibility") except Exception as e: log_error(f"Failed to apply madmom compatibility patch: {e}") # Fix for NumPy 1.20+ compatibility # These attributes are deprecated in newer NumPy versions try: np.float = float # Use built-in float instead np.int = int # Use built-in int instead log_debug("Applied NumPy compatibility fixes for np.float and np.int") except Exception as e: log_debug(f"Note: NumPy compatibility patch not needed: {e}") # Defer all heavy checks to runtime - just assume everything is available for startup SPLEETER_AVAILABLE = True # Will check at runtime USE_BEAT_TRANSFORMER = True # Will check at runtime USE_CHORD_CNN_LSTM = True # Will check at runtime GENIUS_AVAILABLE = True # Will check at runtime log_debug("Deferred model availability checks to runtime for faster startup") # Runtime model availability checks # Model availability check functions moved to utils/model_utils.py # Root route moved to health blueprint # Debug endpoints moved to debug blueprint # Health route moved to health blueprint # Beat detection route moved to beats blueprint # Chord recognition route moved to chords blueprint # BTC chord recognition functions moved to chords blueprint # BTC chord recognition function moved to chords blueprint # BTC chord recognition routes moved to chords blueprint # Check if BTC models are available # BTC availability check function moved to utils/model_utils.py # Global BTC availability check BTC_AVAILABILITY = check_btc_availability() USE_BTC_SL = BTC_AVAILABILITY['sl_available'] USE_BTC_PL = BTC_AVAILABILITY['pl_available'] # Model info route moved to beats blueprint # Lyrics routes moved to lyrics blueprint # Documentation routes moved to docs blueprint # BTC debug endpoints moved to debug blueprint # BTC import test endpoints moved to debug blueprint # Beat detection test routes moved to beats blueprint # Test madmom route moved to beats blueprint # Chord-CNN-LSTM test endpoints moved to debug blueprint # All remaining debug and test endpoints moved to debug blueprint # Test DBN isolation route moved to beats blueprint # Test all models route moved to beats blueprint # YouTube search routes moved to youtube blueprint # Audio extraction routes moved to audio blueprint # Detect beats Firebase route moved to beats blueprint # Firebase chord recognition route moved to chords blueprint if __name__ == '__main__': # Get port from environment variable or default to 5001 for localhost to avoid macOS AirTunes/AirPlay conflicts # Production deployments (Cloud Run) will override this with PORT environment variable port = int(os.environ.get('PORT', 5001)) log_info(f"Starting Flask app on port {port}") log_info("App is ready to serve requests") app.run(host='0.0.0.0', port=port, debug=False, load_dotenv=False)