diff --git a/app.py b/app.py index a91fd1f..be63c51 100644 --- a/app.py +++ b/app.py @@ -3,13 +3,15 @@ Flask server for YouTube Concert Splitter Downloads YouTube videos and splits them into tracks based on a setlist. """ -from flask import Flask, request, render_template, jsonify +from flask import Flask, request, render_template, jsonify, Response import os import re import subprocess import yt_dlp from pydub import AudioSegment from mutagen.easyid3 import EasyID3 +import json +import sys app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', 'your_secret_key_here_change_this') @@ -50,10 +52,10 @@ def download_youtube_audio(url: str, output_folder: str): cookies_file = 'cookies.txt' has_cookies = os.path.isfile(cookies_file) if has_cookies: - print(f"✓ Using cookies from {cookies_file}") + print(f"✓ Using cookies from {cookies_file}", flush=True) else: - print("⚠️ No cookies.txt found - download may fail for some videos") - print(" See COOKIES_INSTRUCTIONS.txt for how to add cookies") + print("⚠️ No cookies.txt found - download may fail for some videos", flush=True) + print(" See COOKIES_INSTRUCTIONS.txt for how to add cookies", flush=True) # Enhanced options to bypass YouTube restrictions ydl_opts = { @@ -150,7 +152,7 @@ def split_audio(mp3_path: str, entries: list, album: str, artist: str, output_di Skip tracks that already exist. Returns list of track info with status (created or skipped). """ - print(f"Loading audio file: {mp3_path}") + print(f"Loading audio file: {mp3_path}", flush=True) audio = AudioSegment.from_file(mp3_path) total_ms = len(audio) @@ -168,7 +170,7 @@ def split_audio(mp3_path: str, entries: list, album: str, artist: str, output_di # Check if file already exists if os.path.isfile(filepath): - print(f"Skipping track {idx}/{len(entries)}: {filename} (already exists)") + print(f"Skipping track {idx}/{len(entries)}: {filename} (already exists)", flush=True) track_results.append({ 'filename': filename, 'status': 'skipped' @@ -177,7 +179,7 @@ def split_audio(mp3_path: str, entries: list, album: str, artist: str, output_di continue # Extract and export segment - print(f"Creating track {idx}/{len(entries)}: {filename}") + print(f"Creating track {idx}/{len(entries)}: {filename}", flush=True) segment = audio[start_ms:end_ms] # Export with 320kbps @@ -192,7 +194,7 @@ def split_audio(mp3_path: str, entries: list, album: str, artist: str, output_di tags['tracknumber'] = str(idx) tags.save() except Exception as e: - print(f"Warning: Could not add ID3 tags to {filename}: {e}") + print(f"Warning: Could not add ID3 tags to {filename}: {e}", flush=True) track_results.append({ 'filename': filename, @@ -207,11 +209,11 @@ def set_permissions(directory: str): """Set directory permissions to 775 recursively.""" try: subprocess.run(['chmod', '-R', '775', directory], check=True) - print(f"Set permissions 775 on {directory}") + print(f"Set permissions 775 on {directory}", flush=True) except subprocess.CalledProcessError as e: - print(f"Warning: Could not set permissions: {e}") + print(f"Warning: Could not set permissions: {e}", flush=True) except FileNotFoundError: - print("Warning: chmod command not found (might be on Windows)") + print("Warning: chmod command not found (might be on Windows)", flush=True) @app.route('/') @@ -222,136 +224,171 @@ def index(): @app.route('/split', methods=['POST']) def split_concert(): - """Handle the split request.""" - try: - # Get form data - url = request.form.get('youtube_url', '').strip() - artist = request.form.get('artist', '').strip() - album = request.form.get('album', '').strip() - setlist_text = request.form.get('setlist', '').strip() + """Handle the split request.""" + # Force flush stdout immediately + sys.stdout.flush() - # Validate required inputs (setlist is OPTIONAL) - if not url: - return jsonify({'error': 'No YouTube URL provided'}), 400 + try: + # Get form data + url = request.form.get('youtube_url', '').strip() + artist = request.form.get('artist', '').strip() + album = request.form.get('album', '').strip() + setlist_text = request.form.get('setlist', '').strip() - if not artist: - return jsonify({'error': 'No artist name provided'}), 400 + print(f"=== Processing Request ===", flush=True) + print(f"URL: {url}", flush=True) + print(f"Artist: {artist}", flush=True) + print(f"Album: {album}", flush=True) - if not album: - return jsonify({'error': 'No album name provided'}), 400 + # Validate required inputs + if not url: + return jsonify({'error': 'No YouTube URL provided'}), 400 - # Sanitize album for directory name - album_sanitized = sanitize(album) + if not artist: + return jsonify({'error': 'No artist name provided'}), 400 - # Create output directory directly in DOWNLOAD_FOLDER - output_dir = os.path.join(DOWNLOAD_FOLDER, album_sanitized) + if not album: + return jsonify({'error': 'No album name provided'}), 400 - # Create directory only if it doesn't exist - if not os.path.exists(output_dir): - os.makedirs(output_dir, exist_ok=True) - print(f"✓ Created album directory: {output_dir}") - else: - print(f"✓ Album directory already exists: {output_dir}") + # Sanitize album for directory name + album_sanitized = sanitize(album) - # Download audio - print(f"Downloading audio from: {url}") - mp3_path, info = download_youtube_audio(url, DOWNLOAD_FOLDER) + # Create output directory + output_dir = os.path.join(DOWNLOAD_FOLDER, album_sanitized) - # DECISION POINT: Empty setlist = Single song mode - if not setlist_text: - print("📀 Single song mode: No setlist provided") + if not os.path.exists(output_dir): + os.makedirs(output_dir, exist_ok=True) + print(f"✓ Created album directory: {output_dir}", flush=True) + else: + print(f"✓ Album directory already exists: {output_dir}", flush=True) - # Use video title as track name - track_title = info.get('title', 'Unknown Track') - filename = f"01 - {sanitize(track_title)}.mp3" - filepath = os.path.join(output_dir, filename) + # Download audio + print(f"Downloading audio from: {url}", flush=True) + sys.stdout.flush() - # Check if file already exists - if os.path.isfile(filepath): - print(f"⊘ Track already exists: {filename}") - os.remove(mp3_path) # Clean up downloaded file + mp3_path, info = download_youtube_audio(url, DOWNLOAD_FOLDER) - track_results = [{ - 'filename': filename, - 'status': 'skipped' - }] - created_count = 0 - skipped_count = 1 - else: - # Move and rename the file (no splitting needed) - print(f"✓ Creating single track: {filename}") + print(f"✓ Download complete: {mp3_path}", flush=True) + sys.stdout.flush() - # Load audio to re-export with proper tags - audio = AudioSegment.from_file(mp3_path) - audio.export(filepath, format='mp3', bitrate='320k') + # DECISION POINT: Empty setlist = Single song mode + if not setlist_text: + print("📀 Single song mode: No setlist provided", flush=True) - # Add ID3 tags - try: - tags = EasyID3(filepath) - tags['title'] = track_title - tags['album'] = album - tags['artist'] = artist - tags['tracknumber'] = '1' - tags.save() - print(f"✓ Added ID3 tags") - except Exception as e: - print(f"⚠️ Warning: Could not add ID3 tags: {e}") + # Use video title as track name + track_title = info.get('title', 'Unknown Track') + filename = f"01 - {sanitize(track_title)}.mp3" + filepath = os.path.join(output_dir, filename) - # Clean up original - os.remove(mp3_path) + # Check if file already exists + if os.path.isfile(filepath): + print(f"⊘ Track already exists: {filename}", flush=True) + os.remove(mp3_path) - track_results = [{ - 'filename': filename, - 'status': 'created' - }] - created_count = 1 - skipped_count = 0 + track_results = [{ + 'filename': filename, + 'status': 'skipped' + }] + created_count = 0 + skipped_count = 1 + else: + print(f"✓ Creating single track: {filename}", flush=True) - else: - # SPLIT MODE: Parse setlist and split audio - print(f"✂️ Split mode: Processing setlist with {len(setlist_text.splitlines())} lines") + # Load audio to re-export with proper tags + audio = AudioSegment.from_file(mp3_path) + audio.export(filepath, format='mp3', bitrate='320k') - # Parse setlist for splitting - try: - entries = parse_setlist(setlist_text) - except ValueError as e: - os.remove(mp3_path) # Clean up downloaded file - return jsonify({'error': f'Setlist parsing error: {str(e)}'}), 400 + # Add ID3 tags + try: + tags = EasyID3(filepath) + tags['title'] = track_title + tags['album'] = album + tags['artist'] = artist + tags['tracknumber'] = '1' + tags.save() + print(f"✓ Added ID3 tags", flush=True) + except Exception as e: + print(f"⚠️ Warning: Could not add ID3 tags: {e}", flush=True) - if not entries: - os.remove(mp3_path) # Clean up downloaded file - return jsonify({'error': 'No valid tracks found in setlist'}), 400 + # Clean up original + os.remove(mp3_path) - # Split audio into tracks (skipping existing ones) - print(f"Splitting into {len(entries)} tracks...") - track_results, created_count, skipped_count = split_audio( - mp3_path, entries, album, artist, output_dir - ) + track_results = [{ + 'filename': filename, + 'status': 'created' + }] + created_count = 1 + skipped_count = 0 - # Clean up original MP3 - print(f"Removing original file: {mp3_path}") - os.remove(mp3_path) + else: + # SPLIT MODE + print(f"✂️ Split mode: Processing setlist with {len(setlist_text.splitlines())} lines", flush=True) - # Set permissions - set_permissions(output_dir) + # Parse setlist + try: + entries = parse_setlist(setlist_text) + except ValueError as e: + os.remove(mp3_path) + return jsonify({'error': f'Setlist parsing error: {str(e)}'}), 400 - # Return success response with detailed track information - return jsonify({ - 'success': True, - 'album': album, - 'artist': artist, - 'total_tracks': len(track_results), - 'created_count': created_count, - 'skipped_count': skipped_count, - 'tracks': track_results, - 'output_dir': output_dir - }) + if not entries: + os.remove(mp3_path) + return jsonify({'error': 'No valid tracks found in setlist'}), 400 + + # Split audio + print(f"Splitting into {len(entries)} tracks...", flush=True) + sys.stdout.flush() + + track_results, created_count, skipped_count = split_audio( + mp3_path, entries, album, artist, output_dir + ) + + # Clean up original + print(f"Removing original file: {mp3_path}", flush=True) + os.remove(mp3_path) + + # Set permissions + set_permissions(output_dir) + + print(f"✓ Processing complete!", flush=True) + print(f" Created: {created_count}, Skipped: {skipped_count}", flush=True) + sys.stdout.flush() + + # Build response + response_data = { + 'success': True, + 'album': album, + 'artist': artist, + 'total_tracks': len(track_results), + 'created_count': created_count, + 'skipped_count': skipped_count, + 'tracks': track_results, + 'output_dir': output_dir + } + + print(f"Sending response: {json.dumps(response_data, indent=2)}", flush=True) + + # Return JSON response with explicit content type + return Response( + json.dumps(response_data), + status=200, + mimetype='application/json' + ) + + except Exception as e: + print(f"❌ Error: {str(e)}", flush=True) + import traceback + traceback.print_exc() + sys.stdout.flush() + + # Return error as JSON + error_response = {'error': str(e)} + return Response( + json.dumps(error_response), + status=500, + mimetype='application/json' + ) - except Exception as e: - print(f"❌ Error: {str(e)}") - import traceback - traceback.print_exc() - return jsonify({'error': str(e)}), 500 if __name__ == '__main__': # Get configuration from environment variables @@ -359,10 +396,8 @@ if __name__ == '__main__': port = int(os.environ.get('FLASK_PORT', 5000)) debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true' - print(f"Starting YouTube Concert Splitter on {host}:{port}") - print(f"Music directory: {DOWNLOAD_FOLDER}") - - # Run on all interfaces, configurable port -# app.run(host=host, port=port, debug=debug) - app.run(host="0.0.0.0", port=port, debug=debug) + print(f"Starting YouTube Concert Splitter on {host}:{port}", flush=True) + print(f"Music directory: {DOWNLOAD_FOLDER}", flush=True) + # Run with threaded=True for better handling of long requests + app.run(host="0.0.0.0", port=port, debug=debug, threaded=True) \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 613e36d..2c682cf 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,166 +1,124 @@ - +
- +Download and split concert videos into individual tracks
+Download and split concerts into individual tracks
-