fix the timeout waiting from web client
This commit is contained in:
283
app.py
283
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)
|
||||
Reference in New Issue
Block a user