From aa4b10c991045a62d803af4bec804ce2bb5ff658 Mon Sep 17 00:00:00 2001 From: tommal Date: Tue, 30 Dec 2025 01:06:33 +0100 Subject: [PATCH] problem with youtube in docker --- Dockerfile | 46 +++++ app.py | 366 ++++++++++++++++++++++++++++++++++++ cookies.txt | 263 ++++++++++++++++++++++++++ docker-compose.yml | 24 +++ requirements.txt | 5 + templates/index.html | 434 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1138 insertions(+) create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 cookies.txt create mode 100644 docker-compose.yml create mode 100644 requirements.txt create mode 100644 templates/index.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0fff559 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# Use Python 3.11 slim image as base +FROM python:3.11-slim + +# Set environment variables +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=1 \ + DEBIAN_FRONTEND=noninteractive + +# Set working directory +WORKDIR /app + +# Install system dependencies including FFmpeg +RUN apt-get update && apt-get install -y --no-install-recommends \ + ffmpeg \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --upgrade pip && \ + pip install -r requirements.txt + +# Copy application files +COPY app.py . +COPY templates/ templates/ + +# Create music directory with proper permissions +RUN mkdir -p /app/youtube && \ + chmod -R 775 /app/youtube + +# Create a non-root user and switch to it +#RUN useradd -m -u 1000 appuser && \ +# chown -R appuser:appuser /app +#USER appuser + +# Expose port +EXPOSE 5000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000')" || exit 1 + +# Run the application +CMD ["python", "app.py"] \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..3bc069c --- /dev/null +++ b/app.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python3 +""" +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 +import os +import re +import subprocess +import yt_dlp +from pydub import AudioSegment +from mutagen.easyid3 import EasyID3 + +app = Flask(__name__) +app.secret_key = os.environ.get('SECRET_KEY', 'your_secret_key_here_change_this') + +# Configuration +DOWNLOAD_FOLDER = os.environ.get('DOWNLOAD_FOLDER', 'youtube') +os.makedirs(DOWNLOAD_FOLDER, exist_ok=True) + + +def sanitize(s: str) -> str: + """Sanitize string for use as filename.""" + s = s.strip() + s = re.sub(r'[\\/:"*?<>|]+', '', s) + s = re.sub(r"[^A-Za-z0-9 _\-]", '_', s) + return s + + +def parse_timestamp(ts: str) -> int: + """Parse timestamp string (HH:MM:SS or MM:SS) to milliseconds.""" + parts = ts.strip().split(':') + try: + if len(parts) == 3: + h, m, s = parts + elif len(parts) == 2: + h = 0 + m, s = parts + else: + raise ValueError(f"Invalid timestamp format: '{ts}'") + return (int(h) * 3600 + int(m) * 60 + int(s)) * 1000 + except ValueError as e: + raise ValueError(f"Cannot parse timestamp '{ts}': {e}") + + +def download_youtube_audio(url: str, output_folder: str): + """Download YouTube video and convert to MP3 at 320kbps with enhanced anti-blocking measures.""" + + # Check if cookies file exists + cookies_file = 'cookies.txt' + has_cookies = os.path.isfile(cookies_file) + if has_cookies: + print(f"✓ Using cookies from {cookies_file}") + else: + print("⚠️ No cookies.txt found - download may fail for some videos") + print(" See COOKIES_INSTRUCTIONS.txt for how to add cookies") + + # Enhanced options to bypass YouTube restrictions + ydl_opts = { + 'format': 'bestaudio/best', + 'outtmpl': f'{output_folder}/%(title)s.%(ext)s', + 'noplaylist': True, + 'quiet': False, + 'no_warnings': False, + 'extract_flat': False, + 'ignoreerrors': False, + + # Use cookies if available + 'cookiefile': cookies_file if has_cookies else None, + + # Anti-blocking measures + 'nocheckcertificate': True, + 'geo_bypass': True, + 'age_limit': None, + + # Better headers to mimic a real browser + 'http_headers': { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-us,en;q=0.5', + 'Accept-Encoding': 'gzip,deflate', + 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'Connection': 'keep-alive', + }, + + # Extractor specific arguments for YouTube + 'extractor_args': { + 'youtube': { + 'player_client': ['android', 'web'], + 'player_skip': ['webpage', 'configs'], + } + }, + + # Post-processing + 'postprocessors': [{ + 'key': 'FFmpegExtractAudio', + 'preferredcodec': 'mp3', + 'preferredquality': '320', + }], + + # Retry options + 'retries': 10, + 'fragment_retries': 10, + 'skip_unavailable_fragments': True, + } + + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=True) + base_name = ydl.prepare_filename(info) + + # Determine the actual MP3 filename + mp3_path = os.path.splitext(base_name)[0] + '.mp3' + + if not os.path.isfile(mp3_path): + raise FileNotFoundError(f"Downloaded MP3 not found: {mp3_path}") + + return mp3_path, info + + +def parse_setlist(setlist_text: str): + """Parse setlist text into list of (timestamp_ms, title) tuples.""" + entries = [] + lines = setlist_text.strip().split('\n') + + for line in lines: + line = line.strip() + if not line: + continue + + # Match pattern: "TIMESTAMP TITLE" + m = re.match(r"(\d+:\d+(?::\d+)?)\s+(.+)", line) + if not m: + raise ValueError(f"Invalid setlist line format: '{line}'") + + ts, title = m.groups() + try: + timestamp_ms = parse_timestamp(ts) + entries.append((timestamp_ms, title.strip())) + except ValueError as e: + raise ValueError(f"Error parsing line '{line}': {e}") + + # Sort by timestamp + entries.sort(key=lambda x: x[0]) + return entries + + +def split_audio(mp3_path: str, entries: list, album: str, artist: str, output_dir: str): + """ + Split audio file into tracks based on setlist entries. + Skip tracks that already exist. + Returns list of track info with status (created or skipped). + """ + print(f"Loading audio file: {mp3_path}") + audio = AudioSegment.from_file(mp3_path) + total_ms = len(audio) + + track_results = [] + created_count = 0 + skipped_count = 0 + + for idx, (start_ms, title) in enumerate(entries, start=1): + # Determine end time + end_ms = entries[idx][0] if idx < len(entries) else total_ms + + # Create filename + filename = f"{idx:02d} - {sanitize(title)}.mp3" + filepath = os.path.join(output_dir, filename) + + # Check if file already exists + if os.path.isfile(filepath): + print(f"Skipping track {idx}/{len(entries)}: {filename} (already exists)") + track_results.append({ + 'filename': filename, + 'status': 'skipped' + }) + skipped_count += 1 + continue + + # Extract and export segment + print(f"Creating track {idx}/{len(entries)}: {filename}") + segment = audio[start_ms:end_ms] + + # Export with 320kbps + segment.export(filepath, format='mp3', bitrate='320k') + + # Add ID3 tags + try: + tags = EasyID3(filepath) + tags['title'] = title + tags['album'] = album + tags['artist'] = artist + tags['tracknumber'] = str(idx) + tags.save() + except Exception as e: + print(f"Warning: Could not add ID3 tags to {filename}: {e}") + + track_results.append({ + 'filename': filename, + 'status': 'created' + }) + created_count += 1 + + return track_results, created_count, skipped_count + + +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}") + except subprocess.CalledProcessError as e: + print(f"Warning: Could not set permissions: {e}") + except FileNotFoundError: + print("Warning: chmod command not found (might be on Windows)") + + +@app.route('/') +def index(): + """Render the main page.""" + return render_template('index.html') + + +@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() + + # Validate required inputs (setlist is OPTIONAL) + if not url: + return jsonify({'error': 'No YouTube URL provided'}), 400 + + if not artist: + return jsonify({'error': 'No artist name provided'}), 400 + + if not album: + return jsonify({'error': 'No album name provided'}), 400 + + # Sanitize album for directory name + album_sanitized = sanitize(album) + + # Create output directory directly in DOWNLOAD_FOLDER + output_dir = os.path.join(DOWNLOAD_FOLDER, album_sanitized) + + # 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}") + + # Download audio + print(f"Downloading audio from: {url}") + mp3_path, info = download_youtube_audio(url, DOWNLOAD_FOLDER) + + # DECISION POINT: Empty setlist = Single song mode + if not setlist_text: + print("📀 Single song mode: No setlist provided") + + # 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) + + # Check if file already exists + if os.path.isfile(filepath): + print(f"⊘ Track already exists: {filename}") + os.remove(mp3_path) # Clean up downloaded file + + 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}") + + # Load audio to re-export with proper tags + audio = AudioSegment.from_file(mp3_path) + audio.export(filepath, format='mp3', bitrate='320k') + + # 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}") + + # Clean up original + os.remove(mp3_path) + + track_results = [{ + 'filename': filename, + 'status': 'created' + }] + created_count = 1 + skipped_count = 0 + + else: + # SPLIT MODE: Parse setlist and split audio + print(f"✂️ Split mode: Processing setlist with {len(setlist_text.splitlines())} lines") + + # 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 + + if not entries: + os.remove(mp3_path) # Clean up downloaded file + return jsonify({'error': 'No valid tracks found in setlist'}), 400 + + # 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 + ) + + # Clean up original MP3 + print(f"Removing original file: {mp3_path}") + os.remove(mp3_path) + + # Set permissions + set_permissions(output_dir) + + # 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 + }) + + 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 + host = os.environ.get('FLASK_HOST', '0.0.0.0') + 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="0.0.0.0", port=port, debug=debug) diff --git a/cookies.txt b/cookies.txt new file mode 100644 index 0000000..1636f4c --- /dev/null +++ b/cookies.txt @@ -0,0 +1,263 @@ +# Netscape HTTP Cookie File +# This file is generated by yt-dlp. Do not edit. + +accounts.google.com FALSE / TRUE 13412705143000000 OTZ +accounts.google.com FALSE / TRUE 13444936467584108 SMSV +accounts.google.com FALSE / TRUE 13446034542734482 __Host-GAPS +ogs.google.com FALSE / TRUE 13412705172000000 OTZ +.standsapp.org TRUE / FALSE 13417889183000000 _fbp +.standsapp.org TRUE / FALSE 13444673186249540 _ga +.standsapp.org TRUE / FALSE 13444673186259278 _ga_YKB0534KDL +.standsapp.org TRUE / FALSE 13417889186000000 _gcl_au +.region1.google-analytics.com TRUE / TRUE 13417889187217774 ar_debug +www.standsapp.org FALSE / TRUE 13441649179130482 pll_language +.youtube.com TRUE / TRUE 1782604482 VISITOR_INFO1_LIVE ObDp_56FMXo +.youtube.com TRUE / TRUE 1782604482 VISITOR_PRIVACY_METADATA CgJJVBIhEh0SGwsMDg8QERITFBUWFxgZGhscHR4fICEiIyQlJiAS +.youtube.com TRUE / TRUE 1782604481 __Secure-ROLLOUT_TOKEN GMyewrv_45ED +.youtube.com TRUE / TRUE 13444936467727630 __Secure-3PAPISID +.youtube.com TRUE / TRUE 13444936467727524 __Secure-3PSID +.youtube.com TRUE / FALSE 0 PREF hl=en&tz=UTC +.youtube.com TRUE / TRUE 13443061673959104 __Secure-1PSIDTS +.youtube.com TRUE / TRUE 13443061673959260 __Secure-3PSIDTS +.youtube.com TRUE / TRUE 13443061731393390 __Secure-3PSIDCC +.youtube.com TRUE / TRUE 0 SOCS CAI +.youtube.com TRUE / TRUE 1767054281 GPS 1 +.youtube.com TRUE / TRUE 1830124482 __Secure-YT_TVFAS t=490847&s=2 +.youtube.com TRUE / TRUE 1782604482 DEVICE_INFO ChxOelU0T1RRek1qWXlNRGczTWpnME5qTTJNdz09EMKpzMoGGMKpzMoG +.youtube.com TRUE / TRUE 0 YSC ir2O5VGnpYY +.youtube.com TRUE /tv TRUE 1799884482 __Secure-YT_DERP CIjqrJfaAg%3D%3D +.google.com TRUE / FALSE 13425665336925712 SEARCH_SAMESITE +.google.com TRUE / TRUE 13425665336925768 __Secure-BUCKET +.google.com TRUE / FALSE 13444936467583980 APISID +.google.com TRUE / FALSE 13444936467583932 HSID +.google.com TRUE / TRUE 13444936467584004 SAPISID +.google.com TRUE / TRUE 13444936467583956 SSID +.google.com TRUE / TRUE 13444936467584030 __Secure-1PAPISID +.google.com TRUE / TRUE 13444936467583818 __Secure-1PSID +.google.com TRUE / TRUE 13444936467584052 __Secure-3PAPISID +.google.com TRUE / TRUE 13444936467583844 __Secure-3PSID +.google.com TRUE / TRUE 13425665336130490 AEC +.google.com TRUE / TRUE 13443061586344152 __Secure-1PSIDTS +.google.com TRUE / TRUE 13443061586344312 __Secure-3PSIDTS +.google.com TRUE / TRUE 13411525885680368 __Secure-STRP +.google.com TRUE / FALSE 13443061648654366 SIDCC +.google.com TRUE / TRUE 13443061648654472 __Secure-1PSIDCC +.google.com TRUE / TRUE 13443061648654508 __Secure-3PSIDCC +extensions.gnome.org FALSE / TRUE 13441564334641682 csrftoken +.gnome-look.org TRUE / FALSE 13425883544000000 _pk_ref.20.5702 +.gnome-look.org TRUE / FALSE 13444070744000000 _pk_id.20.5702 +.gnome-look.org TRUE / FALSE 13410117508000000 _pk_ses.20.5702 +www.gnome-look.org FALSE / FALSE 13410119251490280 verified +mail.google.com FALSE / TRUE 13412709741379250 __Host-GMAIL_SCH_GML +mail.google.com FALSE / TRUE 13412709741379140 __Host-GMAIL_SCH_GMN +mail.google.com FALSE / TRUE 13412709741379232 __Host-GMAIL_SCH_GMS +mail.google.com FALSE / TRUE 13444819212601578 __Secure-OSID +mail.google.com FALSE /mail/u/1 TRUE 13441795214000000 GMAIL_LF +mail.google.com FALSE /mail/u/1 TRUE 13412293260979888 COMPASS +mail.google.com FALSE /chat/u/0 TRUE 13412338995152670 COMPASS +contacts.google.com FALSE / TRUE 13412709746000000 OTZ +.github.com TRUE / TRUE 13441654551289152 _octo +.github.com TRUE / TRUE 13441654551289172 logged_in +.jetbrains.com TRUE / TRUE 13441224316000000 first_utm_parameters +.jetbrains.com TRUE / FALSE 13425740930000000 optimizelySession +.jetbrains.com TRUE / TRUE 13412781128762452 JBA +.jetbrains.com TRUE / FALSE 13442513677000000 _clck +.jetbrains.com TRUE / TRUE 13442081676000000 last_utm_parameters +.jetbrains.com TRUE / TRUE 13417896316524766 FPAU +.jetbrains.com TRUE / FALSE 13411064113000000 _clsk +.jetbrains.com TRUE / TRUE 13445537716281512 _dcid +.jetbrains.com TRUE / TRUE 13445537712344148 _ga +.jetbrains.com TRUE / TRUE 13418753712000000 _rdt_uuid +.jetbrains.com TRUE / FALSE 13426529711000000 optimizelyEndUserId +.jetbrains.com TRUE / TRUE 13445537839448894 _ga_9J976DJZ68 +.jetbrains.com TRUE / TRUE 13445537839440488 _ga_M8TDRLXFQH +www.jetbrains.com FALSE / FALSE 13417896316000000 userToken +.docker.com TRUE / FALSE 13444332791000000 _cs_c +.docker.com TRUE / FALSE 13444728790466464 _ga_XJWPQMJYHQ +.docker.com TRUE / TRUE 13444246390000000 _hp5_event_props.4204607514 +.docker.com TRUE / FALSE 13441704793000000 ajs_anonymous_id +.docker.com TRUE / FALSE 13410170590000000 signals-sdk-session-id +.docker.com TRUE / FALSE 13441704790000000 signals-sdk-user-id +.docker.com TRUE / TRUE 13444246416000000 _hp5_let.4204607514 +.docker.com TRUE / FALSE 13410170664000000 _cs_s +docs.docker.com FALSE / TRUE 13410183190000000 _gd_session +docs.docker.com FALSE / TRUE 13444728790479132 _gd_visitor +.docs.docker.com TRUE / TRUE 13441704791000000 _zitok +www.virtualbox.org FALSE / TRUE 13417946132189528 trac_session +.microsoft.com TRUE / TRUE 13441708111888220 MC1 +.microsoft.com TRUE / TRUE 13410373910819606 MS0 +.microsoft.com TRUE / TRUE 13444068110768244 MUID +code.visualstudio.com FALSE / TRUE 13441708106488964 MSFPC +code.visualstudio.com FALSE / TRUE 13441708113109592 MicrosoftApplicationsTelemetryDeviceId +code.visualstudio.com FALSE / TRUE 13410173913000000 ai_session +support.lenovo.com FALSE / FALSE 13444387305000000 _evidon_consent_cookie +support.lenovo.com FALSE / FALSE 13444300905000000 _evidon_suppress_notification_cookie +.lenovo.com TRUE / FALSE 13444732905363200 s_ecid +.lenovo.com TRUE / TRUE 13441708906000000 QuantumMetricUserID +.ebay.it TRUE / FALSE 13425724918004140 __uzma +.ebay.it TRUE / FALSE 13425724918004160 __uzmb +.ebay.it TRUE / FALSE 13425724918004176 __uzmc +.ebay.it TRUE / FALSE 13425724918004192 __uzmd +.ebay.it TRUE / FALSE 13425724918004208 __uzme +.ebay.it TRUE / TRUE 13444732936840336 dp1 +.ebay.it TRUE / TRUE 13444732936840352 nonsession +.support.lenovo.com TRUE / FALSE 13411468906097544 esupport#lang1 +src.ebay-us.com FALSE / TRUE 13444732921099446 thx_guid +src.ebay-us.com FALSE / TRUE 13444732921099488 tmx_guid +.gemini.google.com TRUE / FALSE 13444733417184702 _ga +.gemini.google.com TRUE / FALSE 13417894934000000 _gcl_au +.anydesk.com TRUE / FALSE 13417964415000000 _gcl_au +.anydesk.com TRUE / TRUE 13410190330763684 __cf_bm +.teamviewer.com TRUE / TRUE 13443884532000000 AMCV_4DBF233B617961000A495FE3%40AdobeOrg +.teamviewer.com TRUE / FALSE 13441724535000000 OptanonAlertBoxClosed +.teamviewer.com TRUE / TRUE 13441724549693132 __bs +.teamviewer.com TRUE / TRUE 13441724531375784 aep_fpid +.teamviewer.com TRUE / TRUE 13444316532605844 kndctr_4DBF233B617961000A495FE3_AdobeOrg_identity +.teamviewer.com TRUE / FALSE 13410274951000000 _clsk +.teamviewer.com TRUE / FALSE 13417964535000000 _gcl_au +.teamviewer.com TRUE / TRUE 13412784154000000 geo-preference +.teamviewer.com TRUE / TRUE 13410190358634336 kndctr_4DBF233B617961000A495FE3_AdobeOrg_cluster +engage.teamviewer.com FALSE /api/in FALSE 13410189536282488 cv-sid +engage.teamviewer.com FALSE /api/in/wg/conf FALSE 13410189535379236 cv-sid +engage.teamviewer.com FALSE /api/in/wg/conf/kbybqvXk3q FALSE 13410189535346756 cv-sid +anydesk.com FALSE / FALSE 13441724530000000 nQ_cookieId +.company-target.com TRUE / TRUE 13444402950598434 tuuid +.company-target.com TRUE / TRUE 13444402950598496 tuuid_lu +myaccount.google.com FALSE / TRUE 13412851140000000 OTZ +www.google.com FALSE / TRUE 13412851140000000 OTZ +chat.google.com FALSE / TRUE 13412851215000000 OTZ +meet.google.com FALSE / TRUE 13444819228917146 OSID +meet.google.com FALSE / TRUE 13444819228917248 __Secure-OSID +chat.deepseek.com FALSE / FALSE 13415444163000000 _gc_usr_id_cs0_d0_sec0_part0 +chat.deepseek.com FALSE / FALSE 13444820164541440 smidV2 +chat.deepseek.com FALSE / FALSE 13410265209000000 _gc_s_cs0_d0_sec0_part0 +.deepseek.com TRUE / FALSE 13441796177000000 ds_cookie_preference +.deepseek.com TRUE / TRUE 13433591100000000 intercom-device-id-guh50jw4 +ubuntu.com FALSE / FALSE 13441798110000000 _cookies_accepted +.ubuntu.com TRUE / FALSE 13441798147000000 _CEFT +.ubuntu.com TRUE / FALSE 13410348537000000 _ce.clock_data +.ubuntu.com TRUE / FALSE 13444822146978580 _ga +.ubuntu.com TRUE / FALSE 13418038110000000 _gcl_au +.ubuntu.com TRUE / FALSE 13444822147120776 _mkto_trk +.ubuntu.com TRUE / TRUE 13418038146000000 _rdt_uuid +.ubuntu.com TRUE / TRUE 13441798147000000 _zitok +.ubuntu.com TRUE / FALSE 13444822698453376 _ga_5LTL1CNEJM +.ubuntu.com TRUE / FALSE 13444822698457298 _ga_PGQQ61N4N6 +centroricerche.sanmarcoweb.com FALSE /csv FALSE 13412876936442220 webUser +drive.google.com FALSE / TRUE 13444906256578716 OSID +drive.google.com FALSE / TRUE 13412938303000000 OTZ +copilot.microsoft.com FALSE / TRUE 13444042968457108 MUID +copilot.microsoft.com FALSE / FALSE 13444042968457168 MUIDB +copilot.microsoft.com FALSE / FALSE 13444042968457236 _EDGE_V +copilot.microsoft.com FALSE / TRUE 13444907053878120 userSidebarOpen +copilot.microsoft.com FALSE / TRUE 13441908111000000 BCP +copilot.microsoft.com FALSE / TRUE 13441908111000000 CMCCP +copilot.microsoft.com FALSE / TRUE 13441908144519756 MicrosoftApplicationsTelemetryDeviceId +copilot.microsoft.com FALSE / TRUE 13410374011000000 ai_session +copilot.microsoft.com FALSE / TRUE 13418149014000000 hasBeenMsalAuthenticated +.chatgpt.com TRUE / FALSE 13441451098894820 oai-did +.chatgpt.com TRUE / FALSE 13425901599933620 oai-allow-ne +.chatgpt.com TRUE / FALSE 13425901599933554 oai_consent_analytics +.chatgpt.com TRUE / FALSE 13425901599933600 oai_consent_marketing +servizi2.inps.it FALSE / FALSE 13441888258739948 cookiesession1 +servizi2.inps.it FALSE / FALSE 13410694722000000 aem_dict_flag +servizi2.inps.it FALSE / FALSE 13413200323000000 s_nr30 +servizi2.inps.it FALSE / FALSE 13445168323020904 s_tslv +servizi2.inps.it FALSE / FALSE 13444563523000000 _pk_id.2.e578 +servizi2.inps.it FALSE / FALSE 13410610125000000 _pk_ses.2.e578 +.demdex.net TRUE / TRUE 13425904258198638 demdex +www.inps.it FALSE / TRUE 13441888258000000 kampyle_userid +www.inps.it FALSE / FALSE 13426376292000000 _pk_ref.2.ff96 +www.inps.it FALSE / FALSE 13410694692000000 aem_dict_flag +www.inps.it FALSE / FALSE 13444307458000000 _pk_id.2.ff96 +www.inps.it FALSE / FALSE 13410610131000000 _pk_ses.2.ff96 +www.inps.it FALSE / TRUE 13442144332000000 kampyleSessionPageCounter +www.inps.it FALSE / TRUE 13442144326000000 kampyleUserSession +www.inps.it FALSE / TRUE 13442144326000000 kampyleUserSessionsCount +www.inps.it FALSE / FALSE 13413200332000000 s_nr30 +www.inps.it FALSE / FALSE 13445168332065730 s_tslv +serviziweb2.inps.it FALSE / FALSE 13441888269938272 cookiesession1 +serviziweb2.inps.it FALSE / FALSE 13426376294000000 _pk_ref.2.7483 +serviziweb2.inps.it FALSE / FALSE 13444307470000000 _pk_id.2.7483 +serviziweb2.inps.it FALSE / FALSE 13413200358000000 s_nr30 +serviziweb2.inps.it FALSE / FALSE 13445168358593804 s_tslv +chatgpt.com FALSE / FALSE 13441891666000000 _dd_s +.c1.microsoft.com TRUE / TRUE 13410976910768284 MR +.c.bing.com TRUE / TRUE 13444068111709472 SRM_B +.c.bing.com TRUE / TRUE 13444068111709486 SRM_I +.login.microsoftonline.com TRUE / TRUE 13444068125000000 brcap +.live.com TRUE / TRUE 13427677343408184 ANON +.live.com TRUE / TRUE 13444068143408148 MSPAuth +.live.com TRUE / TRUE 13444068143408160 MSPProf +.live.com TRUE / TRUE 13419037343408172 NAP +.live.com TRUE / TRUE 13444068143408080 PPLState +.login.live.com TRUE / TRUE 13444068143408258 JSHP +.login.live.com TRUE / TRUE 13444068136160892 MSCC +.login.live.com TRUE / TRUE 13444068143408128 MSPCID +.login.live.com TRUE / TRUE 13444068143408228 SDIDC +login.microsoftonline.com FALSE / TRUE 13412964132162732 fpc +.copilot.microsoft.com TRUE / TRUE 13410374814381838 __cf_bm +.claude.ai TRUE / FALSE 13444936438175780 __ssid +.claude.ai TRUE / TRUE 13444936439527718 _fbp +.claude.ai TRUE / TRUE 13441912439527496 anthropic-consent-preferences +.claude.ai TRUE / TRUE 13443010568393446 CH-prefers-color-scheme +.claude.ai TRUE / FALSE 13443010570000000 ajs_anonymous_id +.claude.ai TRUE / FALSE 13443010570000000 ajs_user_id +.claude.ai TRUE / TRUE 13434804571000000 intercom-device-id-lupk8zyo +.claude.ai TRUE / TRUE 13443010568392894 lastActiveOrg +.claude.ai TRUE / TRUE 13443025659831380 user-sidebar-visible-on-load +.claude.ai TRUE /fc TRUE 13412968441398182 ARID +claude.ai FALSE / FALSE 13425928440000000 g_state +claude.ai FALSE / TRUE 13411518465662864 activitySessionId +claude.ai FALSE / TRUE 13437395265662978 anthropic-device-id +.google.it TRUE / TRUE 13444936467832424 SAPISID +.google.it TRUE / TRUE 13444936467832344 SSID +.google.it TRUE / TRUE 13444936467832470 __Secure-1PAPISID +.google.it TRUE / TRUE 13444936467832512 __Secure-3PAPISID +.google.it TRUE / TRUE 13444936467832254 __Secure-3PSID +eu-west-1.signin.aws FALSE /platform TRUE 13441978750943354 platform-ubid +wiki.sanmarcoweb.com FALSE / FALSE 13426008228983252 wikiSynergyToken +wiki.sanmarcoweb.com FALSE / FALSE 13426008228983230 wikiSynergyUserID +wiki.sanmarcoweb.com FALSE / FALSE 13426008228983242 wikiSynergyUserName +.typeless.now TRUE / FALSE 13445081082653212 _ga +.typeless.now TRUE / FALSE 13445081082652742 _ga_SW8FCPTQ4J +.typeless.now TRUE / FALSE 13418297082000000 _gcl_au +.extensions-hub.com TRUE / TRUE 13410528281387470 _ga_FHLJ7MSZL4 +.idserver.servizicie.interno.gov.it TRUE / TRUE 13410609199447872 JSESSIONID +.inps.it TRUE / TRUE 13442144331000000 lang +.inps.it TRUE / FALSE 13410610158000000 adobeLastVisitedUrl +.inps.it TRUE / TRUE 13410610155000000 kndctr_06F9BFD15A2171460A495CF8_AdobeOrg_cluster +smilearning.sanmarcoweb.com FALSE / TRUE 13426418017014876 my_wiki_wikiToken +smilearning.sanmarcoweb.com FALSE / TRUE 13426418017014768 my_wiki_wikiUserID +smilearning.sanmarcoweb.com FALSE / TRUE 13426418017014860 my_wiki_wikiUserName +securden.sanmarcoweb.com FALSE / TRUE 13442336738332264 securdenpost6549251129127743 +securden.sanmarcoweb.com FALSE / TRUE 13445447138332338 securdensession5561950725146635 +calendar-pa.clients6.google.com FALSE / TRUE 13411835737257050 COMPASS +calendar.google.com FALSE / TRUE 13411841563803676 COMPASS +bitbucket.sanmarcoweb.com FALSE / TRUE 13413569612442948 _atl_bitbucket_remember_me +a26669750187.cdn.optimizely.com FALSE / TRUE 13426529712172480 https://www.jetbrains.com_oeu1765646785917r0.5446460522019785$$26613100737$$session_state +.getpostman.com TRUE / TRUE 13413572325657536 _PUB_ID +.getpostman.com TRUE / FALSE 13445540321620854 _pm.store +.getpostman.com TRUE / FALSE 13418756321823388 _pmt +.getpostman.com TRUE / FALSE 13442516320000000 analytics_session_id +.getpostman.com TRUE / FALSE 13442516320000000 analytics_session_id.last_access +.getpostman.com TRUE / TRUE 13442516325657480 dashboard_beta +.getpostman.com TRUE / TRUE 13413572325657408 getpostmanlogin +.postman.com TRUE / TRUE 13413572327008484 _PUB_ID +.postman.com TRUE / TRUE 13410982127008528 __cf_bm +.postman.com TRUE / FALSE 13418756327008106 _pmt +.postman.com TRUE / TRUE 13442516327008418 dashboard_beta +.postman.com TRUE / TRUE 13413572327008350 getpostmanlogin +.identity.getpostman.com TRUE / TRUE 13410982114779800 __cf_bm +.postman.co TRUE / FALSE 13418756326340216 _pmt +.postman.co TRUE / TRUE 13442516326340450 dashboard_beta +identity.getpostman.com FALSE / TRUE 13444676315290018 dwndvc +identity.getpostman.com FALSE / TRUE 13411066714779750 legacy_sails.sid +identity.postman.co FALSE / TRUE 13411066726340048 legacy_sails.sid +chromewebstore.google.com FALSE / TRUE 13414117587000000 OTZ +.aitopia.ai TRUE / TRUE 13446085577353154 PHPSESSID +.aitopia.ai TRUE / FALSE 13421893577353120 eref +.aitopia.ai TRUE / FALSE 13421893577353136 hopekey +extensions.aitopia.ai FALSE / TRUE 13411527377353168 __cflb +.chromewebstore.google.com TRUE / FALSE 13446085645754266 _ga +.chromewebstore.google.com TRUE / FALSE 13446085669817496 _ga_KHZNC1Q6K0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0a63b7a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.8' + +services: + youtube-splitter: + build: . + container_name: youtube-concert-splitter + ports: + - "5000:5000" + volumes: + # Mount server's music directory to container + - /mnt/media/music:/app/youtube + # Optional: Mount cookies.txt if you have it + - ./cookies.txt:/app/cookies.txt + environment: + - FLASK_ENV=production + - PYTHONUNBUFFERED=1 + - DOWNLOAD_FOLDER=/app/youtube + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f5b5dfa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Flask==3.0.0 +yt-dlp==2024.12.6 +pydub==0.25.1 +mutagen==1.47.0 +Werkzeug==3.0.1 \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..77d8bd4 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,434 @@ + + + + + YouTube Concert Splitter + + + +
+

🎵 YouTube Concert Splitter

+

Download and split concert videos into individual tracks

+ +
+
+ + +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + +
+ Split Mode: Enter "TIMESTAMP TRACK_TITLE" per line (e.g., "0:00 Song Name")
+ Single Mode: Leave this field completely empty to download entire video as one track +
+
+ + +
+ +
+
+
+ + + + + + \ No newline at end of file