fix the timeout waiting from web client

This commit is contained in:
2026-01-02 18:35:25 +01:00
parent 92f9b110ce
commit 492d336ee3
2 changed files with 433 additions and 561 deletions

283
app.py
View File

@@ -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)