forked from computertech/techdj
Upload files to "/"
This commit is contained in:
272
server.py
Normal file
272
server.py
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# Monkey patch MUST be first - before any other imports!
|
||||||
|
import eventlet
|
||||||
|
eventlet.monkey_patch()
|
||||||
|
|
||||||
|
import os
|
||||||
|
from flask import Flask, send_from_directory, jsonify, request, session
|
||||||
|
from flask_socketio import SocketIO, emit
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
# Load environment variables from .env file
|
||||||
|
load_dotenv()
|
||||||
|
import downloader
|
||||||
|
|
||||||
|
# Relay State
|
||||||
|
broadcast_state = {
|
||||||
|
'active': False
|
||||||
|
}
|
||||||
|
listener_count = 0
|
||||||
|
MUSIC_FOLDER = "music"
|
||||||
|
# Ensure music folder exists
|
||||||
|
if not os.path.exists(MUSIC_FOLDER):
|
||||||
|
os.makedirs(MUSIC_FOLDER)
|
||||||
|
|
||||||
|
# Helper for shared routes
|
||||||
|
def setup_shared_routes(app):
|
||||||
|
@app.route('/library.json')
|
||||||
|
def get_library():
|
||||||
|
library = []
|
||||||
|
if os.path.exists(MUSIC_FOLDER):
|
||||||
|
for filename in sorted(os.listdir(MUSIC_FOLDER)):
|
||||||
|
if filename.lower().endswith(('.mp3', '.m4a', '.wav', '.flac', '.ogg')):
|
||||||
|
library.append({
|
||||||
|
"title": os.path.splitext(filename)[0],
|
||||||
|
"file": f"music/{filename}"
|
||||||
|
})
|
||||||
|
return jsonify(library)
|
||||||
|
|
||||||
|
@app.route('/download', methods=['POST'])
|
||||||
|
def download():
|
||||||
|
data = request.json
|
||||||
|
url = data.get('url')
|
||||||
|
quality = data.get('quality', '320')
|
||||||
|
if not url:
|
||||||
|
return jsonify({"success": False, "error": "No URL provided"}), 400
|
||||||
|
result = downloader.download_mp3(url, quality)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
|
@app.route('/search_youtube', methods=['GET'])
|
||||||
|
def search_youtube():
|
||||||
|
query = request.args.get('q', '')
|
||||||
|
if not query:
|
||||||
|
return jsonify({"success": False, "error": "No query provided"}), 400
|
||||||
|
|
||||||
|
# Get API key from environment variable
|
||||||
|
api_key = os.environ.get('YOUTUBE_API_KEY', '')
|
||||||
|
if not api_key:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"error": "YouTube API key not configured. Set YOUTUBE_API_KEY environment variable."
|
||||||
|
}), 500
|
||||||
|
|
||||||
|
try:
|
||||||
|
import requests
|
||||||
|
# Search YouTube using Data API v3
|
||||||
|
url = 'https://www.googleapis.com/youtube/v3/search'
|
||||||
|
params = {
|
||||||
|
'part': 'snippet',
|
||||||
|
'q': query,
|
||||||
|
'type': 'video',
|
||||||
|
'videoCategoryId': '10', # Music category
|
||||||
|
'maxResults': 20,
|
||||||
|
'key': api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.get(url, params=params)
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if 'error' in data:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"error": data['error'].get('message', 'YouTube API error')
|
||||||
|
}), 400
|
||||||
|
|
||||||
|
# Format results
|
||||||
|
results = []
|
||||||
|
for item in data.get('items', []):
|
||||||
|
results.append({
|
||||||
|
'videoId': item['id']['videoId'],
|
||||||
|
'title': item['snippet']['title'],
|
||||||
|
'channel': item['snippet']['channelTitle'],
|
||||||
|
'thumbnail': item['snippet']['thumbnails']['medium']['url'],
|
||||||
|
'url': f"https://www.youtube.com/watch?v={item['id']['videoId']}"
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({"success": True, "results": results})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"success": False, "error": str(e)}), 500
|
||||||
|
|
||||||
|
@app.route('/<path:filename>')
|
||||||
|
def serve_static(filename):
|
||||||
|
response = send_from_directory('.', filename)
|
||||||
|
if filename.endswith(('.css', '.js', '.html')):
|
||||||
|
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return send_from_directory('.', 'index.html')
|
||||||
|
|
||||||
|
@app.route('/upload', methods=['POST'])
|
||||||
|
def upload_file():
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return jsonify({"success": False, "error": "No file provided"}), 400
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({"success": False, "error": "No file selected"}), 400
|
||||||
|
|
||||||
|
if not file.filename.endswith('.mp3'):
|
||||||
|
return jsonify({"success": False, "error": "Only MP3 files are allowed"}), 400
|
||||||
|
|
||||||
|
# Sanitize filename
|
||||||
|
import re
|
||||||
|
filename = re.sub(r'[^\w\s-]', '', file.filename)
|
||||||
|
filename = re.sub(r'[-\s]+', '-', filename)
|
||||||
|
|
||||||
|
filepath = os.path.join(MUSIC_FOLDER, filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
file.save(filepath)
|
||||||
|
print(f"✅ Uploaded: {filename}")
|
||||||
|
return jsonify({"success": True, "filename": filename})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Upload error: {e}")
|
||||||
|
return jsonify({"success": False, "error": str(e)}), 500
|
||||||
|
|
||||||
|
# === DJ SERVER (Port 5000) ===
|
||||||
|
dj_app = Flask(__name__, static_folder='.', static_url_path='')
|
||||||
|
dj_app.config['SECRET_KEY'] = 'dj_panel_secret'
|
||||||
|
setup_shared_routes(dj_app)
|
||||||
|
dj_socketio = SocketIO(
|
||||||
|
dj_app,
|
||||||
|
cors_allowed_origins="*",
|
||||||
|
async_mode='eventlet',
|
||||||
|
max_http_buffer_size=1e8, # 100MB buffer
|
||||||
|
ping_timeout=10,
|
||||||
|
ping_interval=5,
|
||||||
|
logger=False,
|
||||||
|
engineio_logger=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@dj_socketio.on('connect')
|
||||||
|
def dj_connect():
|
||||||
|
print(f"🎧 DJ connected: {request.sid}")
|
||||||
|
session['is_dj'] = True
|
||||||
|
|
||||||
|
@dj_socketio.on('disconnect')
|
||||||
|
def dj_disconnect():
|
||||||
|
if session.get('is_dj'):
|
||||||
|
print("⚠️ DJ disconnected - broadcast will continue until manually stopped")
|
||||||
|
session['is_dj'] = False
|
||||||
|
# Don't stop streaming_active - let it continue
|
||||||
|
# Broadcast will resume when DJ reconnects
|
||||||
|
|
||||||
|
def stop_broadcast_after_timeout():
|
||||||
|
"""No longer used - broadcasts don't auto-stop"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dj_socketio.on('start_broadcast')
|
||||||
|
def dj_start():
|
||||||
|
broadcast_state['active'] = True
|
||||||
|
session['is_dj'] = True
|
||||||
|
print("🎙️ Broadcast -> ACTIVE")
|
||||||
|
|
||||||
|
listener_socketio.emit('broadcast_started', namespace='/')
|
||||||
|
listener_socketio.emit('stream_status', {'active': True}, namespace='/')
|
||||||
|
|
||||||
|
@dj_socketio.on('stop_broadcast')
|
||||||
|
def dj_stop():
|
||||||
|
broadcast_state['active'] = False
|
||||||
|
session['is_dj'] = False
|
||||||
|
print("🛑 DJ stopped broadcasting")
|
||||||
|
|
||||||
|
listener_socketio.emit('broadcast_stopped', namespace='/')
|
||||||
|
listener_socketio.emit('stream_status', {'active': False}, namespace='/')
|
||||||
|
|
||||||
|
@dj_socketio.on('audio_chunk')
|
||||||
|
def dj_audio(data):
|
||||||
|
# Relay audio chunk to all listeners immediately
|
||||||
|
if broadcast_state['active']:
|
||||||
|
listener_socketio.emit('audio_data', data, namespace='/')
|
||||||
|
|
||||||
|
# === LISTENER SERVER (Port 6000) ===
|
||||||
|
listener_app = Flask(__name__, static_folder='.', static_url_path='')
|
||||||
|
listener_app.config['SECRET_KEY'] = 'listener_secret'
|
||||||
|
setup_shared_routes(listener_app)
|
||||||
|
listener_socketio = SocketIO(
|
||||||
|
listener_app,
|
||||||
|
cors_allowed_origins="*",
|
||||||
|
async_mode='eventlet',
|
||||||
|
max_http_buffer_size=1e8, # 100MB buffer
|
||||||
|
ping_timeout=10,
|
||||||
|
ping_interval=5,
|
||||||
|
logger=False,
|
||||||
|
engineio_logger=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@listener_socketio.on('connect')
|
||||||
|
def listener_connect():
|
||||||
|
print(f"👂 Listener Socket Connected: {request.sid}")
|
||||||
|
|
||||||
|
@listener_socketio.on('disconnect')
|
||||||
|
def listener_disconnect():
|
||||||
|
global listener_count
|
||||||
|
if session.get('is_listener'):
|
||||||
|
# Clear session flag FIRST to prevent re-entry issues
|
||||||
|
session['is_listener'] = False
|
||||||
|
listener_count = max(0, listener_count - 1)
|
||||||
|
print(f"❌ Listener left. Total: {listener_count}")
|
||||||
|
# Broadcast to all listeners
|
||||||
|
listener_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
|
||||||
|
# Broadcast to all DJs
|
||||||
|
dj_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
|
||||||
|
|
||||||
|
@listener_socketio.on('join_listener')
|
||||||
|
def listener_join():
|
||||||
|
global listener_count
|
||||||
|
if not session.get('is_listener'):
|
||||||
|
session['is_listener'] = True
|
||||||
|
listener_count += 1
|
||||||
|
print(f"👂 New listener joined. Total: {listener_count}")
|
||||||
|
# Broadcast to all listeners
|
||||||
|
listener_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
|
||||||
|
# Broadcast to all DJs
|
||||||
|
dj_socketio.emit('listener_count', {'count': listener_count}, namespace='/')
|
||||||
|
|
||||||
|
emit('stream_status', {'active': broadcast_state['active']})
|
||||||
|
|
||||||
|
@listener_socketio.on('request_header')
|
||||||
|
def handle_request_header():
|
||||||
|
# Header logic removed for local relay mode
|
||||||
|
pass
|
||||||
|
|
||||||
|
@listener_socketio.on('get_listener_count')
|
||||||
|
def listener_get_count():
|
||||||
|
emit('listener_count', {'count': listener_count})
|
||||||
|
|
||||||
|
# DJ Panel Routes (No engine commands needed in local mode)
|
||||||
|
@dj_socketio.on('get_mixer_status')
|
||||||
|
def get_mixer_status():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dj_socketio.on('audio_sync_queue')
|
||||||
|
def audio_sync_queue(data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("=" * 50)
|
||||||
|
print("🎧 TECHDJ PRO - DUAL PORT ARCHITECTURE")
|
||||||
|
print("=" * 50)
|
||||||
|
print("👉 DJ PANEL: http://localhost:5000")
|
||||||
|
print("👉 LISTEN PAGE: http://localhost:5001")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# Audio engine DISABLED
|
||||||
|
print("✅ Local Radio server ready")
|
||||||
|
|
||||||
|
# Run both servers using eventlet's spawn
|
||||||
|
eventlet.spawn(dj_socketio.run, dj_app, host='0.0.0.0', port=5000, debug=False)
|
||||||
|
listener_socketio.run(listener_app, host='0.0.0.0', port=5001, debug=False)
|
||||||
Reference in New Issue
Block a user