import eventlet eventlet.monkey_patch() import logging import os import yt_dlp from flask import Flask, request from flask_socketio import SocketIO from flask_cors import CORS from spotipy import Spotify from spotipy.oauth2 import SpotifyClientCredentials # Configure logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) # Initialize Flask app = Flask(__name__) CORS(app) # Initialize SocketIO socketio = SocketIO( app, cors_allowed_origins="*", async_mode='eventlet', logger=True, engineio_logger=True ) # Initialize Spotify client spotify = Spotify(client_credentials_manager=SpotifyClientCredentials()) # Store active downloads active_downloads = {} class DownloadLogger: def debug(self, msg): if msg.startswith('[download]'): progress = msg.split('%')[0] if '[download]' in progress: progress = progress.split('[download]')[1] try: progress = float(progress.strip()) return progress except: pass return None def warning(self, msg): logger.warning(msg) def error(self, msg): logger.error(msg) def download_progress_hook(d): if d['status'] == 'downloading': progress = float(d['downloaded_bytes'] / d['total_bytes'] * 100) url = d['info_dict']['webpage_url'] if url in active_downloads: active_downloads[url]['progress'] = progress socketio.emit('update_status', { 'url': url, 'progress': progress, 'status': 'Downloading...' }) elif d['status'] == 'finished': url = d['info_dict']['webpage_url'] if url in active_downloads: active_downloads[url]['status'] = 'Processing...' socketio.emit('update_status', { 'url': url, 'progress': 100, 'status': 'Complete' }) @socketio.on('connect') def handle_connect(): logger.info(f'Client connected from {request.remote_addr}') socketio.emit('connection_status', {'status': 'connected'}) @socketio.on('search') def handle_search(data): logger.info(f'Search request received: {data}') query = data.get('query', '').strip() search_type = data.get('type', 'track') try: # Search YouTube ydl_opts = { 'quiet': True, 'no_warnings': True, 'extract_flat': True } with yt_dlp.YoutubeDL(ydl_opts) as ydl: youtube_results = ydl.extract_info(f'ytsearch5:{query}', download=False) tracks = [] for entry in youtube_results['entries']: if entry: tracks.append({ 'id': entry['id'], 'name': entry['title'], 'artist': entry.get('uploader', 'Unknown'), 'type': 'track', 'url': f'https://youtube.com/watch?v={entry["id"]}', 'image': entry.get('thumbnail', ''), 'duration': entry.get('duration', 0) * 1000 }) # Also search Spotify if credentials are available try: spotify_results = spotify.search(q=query, type='track', limit=5) for track in spotify_results['tracks']['items']: tracks.append({ 'id': track['id'], 'name': track['name'], 'artist': track['artists'][0]['name'], 'type': 'track', 'url': track['external_urls']['spotify'], 'image': track['album']['images'][0]['url'] if track['album']['images'] else '', 'duration': track['duration_ms'] }) except Exception as e: logger.warning(f'Spotify search failed: {e}') logger.info(f'Sending {len(tracks)} results') socketio.emit('search_results', { 'query': query, 'type': search_type, 'results': tracks }) except Exception as e: logger.error(f'Search error: {e}') socketio.emit('search_error', {'message': str(e)}) @socketio.on('download') def handle_download(data): logger.info(f'Download request received: {data}') url = data.get('url') item_name = data.get('name', '') if not url: socketio.emit('toast', {'title': 'Error', 'body': 'No URL provided'}) return # Store download info active_downloads[url] = { 'name': item_name, 'status': 'Starting...', 'progress': 0 } def do_download(): try: ydl_opts = { 'format': 'bestaudio/best', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192', }], 'progress_hooks': [download_progress_hook], 'logger': DownloadLogger(), 'outtmpl': f'downloads/%(title)s.%(ext)s' } with yt_dlp.YoutubeDL(ydl_opts) as ydl: ydl.download([url]) # Update final status active_downloads[url]['status'] = 'Complete' active_downloads[url]['progress'] = 100 socketio.emit('update_status', { 'url': url, 'progress': 100, 'status': 'Complete' }) except Exception as e: logger.error(f'Download error: {e}') active_downloads[url]['status'] = 'Error' socketio.emit('update_status', { 'url': url, 'progress': 0, 'status': f'Error: {str(e)}' }) # Start download in background eventlet.spawn(do_download) if __name__ == '__main__': # Create downloads directory os.makedirs('downloads', exist_ok=True) # Start server logger.info('Starting server on port 5140...') socketio.run(app, host='0.0.0.0', port=5140, debug=True)