From 3a1315cdbc03fa6405afde478839aae9334916c7 Mon Sep 17 00:00:00 2001 From: "cool.gitter.choco" Date: Fri, 31 Jan 2025 19:45:55 -0600 Subject: [PATCH] added real time downloading --- routes/album.py | 13 +++- routes/playlist.py | 14 +++- routes/track.py | 72 ++++++++++++++++-- .../__pycache__/__init__.cpython-312.pyc | Bin 146 -> 145 bytes .../__pycache__/credentials.cpython-312.pyc | Bin 6605 -> 6604 bytes .../utils/__pycache__/search.cpython-312.pyc | Bin 609 -> 608 bytes routes/utils/album.py | 25 +++--- routes/utils/playlist.py | 23 +++--- routes/utils/track.py | 25 +++--- static/js/app.js | 19 ++++- templates/index.html | 7 ++ 11 files changed, 149 insertions(+), 49 deletions(-) diff --git a/routes/album.py b/routes/album.py index 0b6e86a..d480eb6 100755 --- a/routes/album.py +++ b/routes/album.py @@ -27,7 +27,7 @@ class FlushingFileWrapper: def flush(self): self.file.flush() -def download_task(service, url, main, fallback, quality, fall_quality, prg_path): +def download_task(service, url, main, fallback, quality, fall_quality, real_time, prg_path): try: from routes.utils.album import download_album with open(prg_path, 'w') as f: @@ -42,7 +42,8 @@ def download_task(service, url, main, fallback, quality, fall_quality, prg_path) main=main, fallback=fallback, quality=quality, - fall_quality=fall_quality + fall_quality=fall_quality, + real_time=real_time ) flushing_file.write(json.dumps({"status": "complete"}) + "\n") except Exception as e: @@ -71,6 +72,10 @@ def handle_download(): fallback = request.args.get('fallback') quality = request.args.get('quality') fall_quality = request.args.get('fall_quality') + + # Retrieve and normalize the real_time parameter; defaults to False. + real_time_arg = request.args.get('real_time', 'false') + real_time = real_time_arg.lower() in ['true', '1', 'yes'] # Sanitize main and fallback to prevent directory traversal if main: @@ -142,11 +147,11 @@ def handle_download(): Process( target=download_task, - args=(service, url, main, fallback, quality, fall_quality, prg_path) + args=(service, url, main, fallback, quality, fall_quality, real_time, prg_path) ).start() return Response( json.dumps({"prg_file": filename}), status=202, mimetype='application/json' - ) \ No newline at end of file + ) diff --git a/routes/playlist.py b/routes/playlist.py index 21e9456..0afb725 100755 --- a/routes/playlist.py +++ b/routes/playlist.py @@ -26,7 +26,7 @@ class FlushingFileWrapper: def flush(self): self.file.flush() -def download_task(service, url, main, fallback, quality, fall_quality, prg_path): +def download_task(service, url, main, fallback, quality, fall_quality, real_time, prg_path): try: from routes.utils.playlist import download_playlist with open(prg_path, 'w') as f: @@ -41,7 +41,8 @@ def download_task(service, url, main, fallback, quality, fall_quality, prg_path) main=main, fallback=fallback, quality=quality, - fall_quality=fall_quality + fall_quality=fall_quality, + real_time=real_time ) flushing_file.write(json.dumps({"status": "complete"}) + "\n") except Exception as e: @@ -71,6 +72,11 @@ def handle_download(): quality = request.args.get('quality') fall_quality = request.args.get('fall_quality') + # Retrieve the real_time parameter from the request query string. + # Here, if real_time is provided as "true", "1", or "yes" (case-insensitive) it will be interpreted as True. + real_time_str = request.args.get('real_time', 'false').lower() + real_time = real_time_str in ['true', '1', 'yes'] + if not all([service, url, main]): return Response( json.dumps({"error": "Missing parameters"}), @@ -85,11 +91,11 @@ def handle_download(): Process( target=download_task, - args=(service, url, main, fallback, quality, fall_quality, prg_path) + args=(service, url, main, fallback, quality, fall_quality, real_time, prg_path) ).start() return Response( json.dumps({"prg_file": filename}), status=202, mimetype='application/json' - ) \ No newline at end of file + ) diff --git a/routes/track.py b/routes/track.py index 1f116bc..9b915b6 100755 --- a/routes/track.py +++ b/routes/track.py @@ -18,6 +18,7 @@ class FlushingFileWrapper: self.file = file def write(self, text): + # Write only lines that start with a JSON object for line in text.split('\n'): if line.startswith('{'): self.file.write(line + '\n') @@ -26,13 +27,13 @@ class FlushingFileWrapper: def flush(self): self.file.flush() -def download_task(service, url, main, fallback, quality, fall_quality, prg_path): +def download_task(service, url, main, fallback, quality, fall_quality, real_time, prg_path): try: from routes.utils.track import download_track with open(prg_path, 'w') as f: flushing_file = FlushingFileWrapper(f) original_stdout = sys.stdout - sys.stdout = flushing_file # Redirect stdout per process + sys.stdout = flushing_file # Redirect stdout for this process try: download_track( @@ -41,7 +42,8 @@ def download_task(service, url, main, fallback, quality, fall_quality, prg_path) main=main, fallback=fallback, quality=quality, - fall_quality=fall_quality + fall_quality=fall_quality, + real_time=real_time ) flushing_file.write(json.dumps({"status": "complete"}) + "\n") except Exception as e: @@ -71,6 +73,16 @@ def handle_download(): quality = request.args.get('quality') fall_quality = request.args.get('fall_quality') + # Retrieve and normalize the real_time parameter; defaults to False. + real_time_arg = request.args.get('real_time', 'false') + real_time = real_time_arg.lower() in ['true', '1', 'yes'] + + # Sanitize main and fallback to prevent directory traversal + if main: + main = os.path.basename(main) + if fallback: + fallback = os.path.basename(fallback) + if not all([service, url, main]): return Response( json.dumps({"error": "Missing parameters"}), @@ -78,6 +90,56 @@ def handle_download(): mimetype='application/json' ) + # Validate credentials based on service and fallback + try: + if service == 'spotify': + if fallback: + # Validate Deezer main credentials and Spotify fallback credentials + deezer_creds_path = os.path.abspath(os.path.join('./creds/deezer', main, 'credentials.json')) + if not os.path.isfile(deezer_creds_path): + return Response( + json.dumps({"error": "Invalid Deezer credentials directory"}), + status=400, + mimetype='application/json' + ) + spotify_fallback_path = os.path.abspath(os.path.join('./creds/spotify', fallback, 'credentials.json')) + if not os.path.isfile(spotify_fallback_path): + return Response( + json.dumps({"error": "Invalid Spotify fallback credentials directory"}), + status=400, + mimetype='application/json' + ) + else: + # Validate Spotify main credentials + spotify_creds_path = os.path.abspath(os.path.join('./creds/spotify', main, 'credentials.json')) + if not os.path.isfile(spotify_creds_path): + return Response( + json.dumps({"error": "Invalid Spotify credentials directory"}), + status=400, + mimetype='application/json' + ) + elif service == 'deezer': + # Validate Deezer main credentials + deezer_creds_path = os.path.abspath(os.path.join('./creds/deezer', main, 'credentials.json')) + if not os.path.isfile(deezer_creds_path): + return Response( + json.dumps({"error": "Invalid Deezer credentials directory"}), + status=400, + mimetype='application/json' + ) + else: + return Response( + json.dumps({"error": "Unsupported service"}), + status=400, + mimetype='application/json' + ) + except Exception as e: + return Response( + json.dumps({"error": f"Credential validation failed: {str(e)}"}), + status=500, + mimetype='application/json' + ) + filename = generate_random_filename() prg_dir = './prgs' os.makedirs(prg_dir, exist_ok=True) @@ -85,11 +147,11 @@ def handle_download(): Process( target=download_task, - args=(service, url, main, fallback, quality, fall_quality, prg_path) + args=(service, url, main, fallback, quality, fall_quality, real_time, prg_path) ).start() return Response( json.dumps({"prg_file": filename}), status=202, mimetype='application/json' - ) \ No newline at end of file + ) diff --git a/routes/utils/__pycache__/__init__.cpython-312.pyc b/routes/utils/__pycache__/__init__.cpython-312.pyc index 9e8d61186f469d9a04b713a0059b6baaa0d4880e..ac1ba2807a1339cb8e7c304f714fee9d1c599a1f 100644 GIT binary patch delta 27 hcmbQlIFXV2G%qg~0}w2&n=+Bxn9*ROz4*j%YXDl`2H^kz delta 27 hcmbQpIEj(_G%qg~0}vSenlX{vn9*>ez4*jX8vt7O2J8R; diff --git a/routes/utils/__pycache__/credentials.cpython-312.pyc b/routes/utils/__pycache__/credentials.cpython-312.pyc index 83bcb6a4d4bef65df58e7a113428856a4b14b6ff..e074b6bfcde02b67f1d544c5567117396919a084 100644 GIT binary patch delta 49 zcmX?We8!mjG%qg~0}!nGIejDdD`s(H{fzwFRQ-zlc=G%qg~0}!MYPus}t$0)9+pOK%Ns$Y?xoS&DUoS|P_kYAEnl{&eQ@dE%| Cg%BM8 delta 49 zcmaFB@{oo5G%qg~0}vSenz51Fk5NKjKO;XkRlg!XIX^EyIYYm=AipHDDz#{G0pkY% Dc{~u~ diff --git a/routes/utils/album.py b/routes/utils/album.py index ca9e4a7..f195546 100755 --- a/routes/utils/album.py +++ b/routes/utils/album.py @@ -4,15 +4,14 @@ import traceback from deezspot.spotloader import SpoLogin from deezspot.deezloader import DeeLogin -def download_album(service, url, main, fallback=None, quality=None, fall_quality=None): +def download_album(service, url, main, fallback=None, quality=None, fall_quality=None, real_time=False): try: if service == 'spotify': - if fallback: if quality is None: quality = 'FLAC' if fall_quality is None: - fall_quality='HIGH' + fall_quality = 'HIGH' # First attempt: use DeeLogin's download_albumspo with the 'main' (Deezer credentials) try: # Load Deezer credentials from 'main' under deezer directory @@ -24,7 +23,7 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality dl = DeeLogin( arl=deezer_creds.get('arl', ''), ) - # Download using download_albumspo + # Download using download_albumspo; pass real_time_dl accordingly dl.download_albumspo( link_album=url, output_dir="./downloads", @@ -33,7 +32,8 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality recursive_download=False, not_interface=False, make_zip=False, - method_save=1 + method_save=1, + real_time_dl=real_time ) except Exception as e: # Load fallback Spotify credentials and attempt download @@ -49,7 +49,8 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality recursive_download=False, not_interface=False, method_save=1, - make_zip=False + make_zip=False, + real_time_dl=real_time ) except Exception as e2: # If fallback also fails, raise an error indicating both attempts failed @@ -60,7 +61,7 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality else: # Original behavior: use Spotify main if quality is None: - quality ='HIGH' + quality = 'HIGH' creds_dir = os.path.join('./creds/spotify', main) credentials_path = os.path.abspath(os.path.join(creds_dir, 'credentials.json')) spo = SpoLogin(credentials_path=credentials_path) @@ -72,11 +73,12 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality recursive_download=False, not_interface=False, method_save=1, - make_zip=False + make_zip=False, + real_time_dl=real_time ) elif service == 'deezer': if quality is None: - quality='FLAC' + quality = 'FLAC' # Existing code remains the same, ignoring fallback creds_dir = os.path.join('./creds/deezer', main) creds_path = os.path.abspath(os.path.join(creds_dir, 'credentials.json')) @@ -92,10 +94,11 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality recursive_quality=True, recursive_download=False, method_save=1, - make_zip=False + make_zip=False, + real_time_dl=real_time ) else: raise ValueError(f"Unsupported service: {service}") except Exception as e: traceback.print_exc() - raise # Re-raise the exception after logging \ No newline at end of file + raise # Re-raise the exception after logging diff --git a/routes/utils/playlist.py b/routes/utils/playlist.py index 91a8809..1f9b426 100755 --- a/routes/utils/playlist.py +++ b/routes/utils/playlist.py @@ -4,15 +4,14 @@ import traceback from deezspot.spotloader import SpoLogin from deezspot.deezloader import DeeLogin -def download_playlist(service, url, main, fallback=None, quality=None, fall_quality=None): +def download_playlist(service, url, main, fallback=None, quality=None, fall_quality=None, real_time=False): try: - if service == 'spotify': if fallback: if quality is None: quality = 'FLAC' if fall_quality is None: - fall_quality='HIGH' + fall_quality = 'HIGH' # First attempt: use DeeLogin's download_playlistspo with the 'main' (Deezer credentials) try: # Load Deezer credentials from 'main' under deezer directory @@ -33,7 +32,8 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual recursive_download=False, not_interface=False, make_zip=False, - method_save=1 + method_save=1, + real_time_dl=real_time ) except Exception as e: # Load fallback Spotify credentials and attempt download @@ -49,7 +49,8 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual recursive_download=False, not_interface=False, method_save=1, - make_zip=False + make_zip=False, + real_time_dl=real_time ) except Exception as e2: # If fallback also fails, raise an error indicating both attempts failed @@ -60,7 +61,7 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual else: # Original behavior: use Spotify main if quality is None: - quality='HIGH' + quality = 'HIGH' creds_dir = os.path.join('./creds/spotify', main) credentials_path = os.path.abspath(os.path.join(creds_dir, 'credentials.json')) spo = SpoLogin(credentials_path=credentials_path) @@ -72,11 +73,12 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual recursive_download=False, not_interface=False, method_save=1, - make_zip=False + make_zip=False, + real_time_dl=real_time ) elif service == 'deezer': if quality is None: - quality='FLAC' + quality = 'FLAC' # Existing code for Deezer, using main as Deezer account creds_dir = os.path.join('./creds/deezer', main) creds_path = os.path.abspath(os.path.join(creds_dir, 'credentials.json')) @@ -92,10 +94,11 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual recursive_quality=False, recursive_download=False, method_save=1, - make_zip=False + make_zip=False, + real_time_dl=real_time ) else: raise ValueError(f"Unsupported service: {service}") except Exception as e: traceback.print_exc() - raise # Re-raise the exception after logging \ No newline at end of file + raise # Re-raise the exception after logging diff --git a/routes/utils/track.py b/routes/utils/track.py index 96ae7bd..467e2ec 100755 --- a/routes/utils/track.py +++ b/routes/utils/track.py @@ -4,15 +4,14 @@ import traceback from deezspot.spotloader import SpoLogin from deezspot.deezloader import DeeLogin -def download_track(service, url, main, fallback=None, quality=None, fall_quality=None): +def download_track(service, url, main, fallback=None, quality=None, fall_quality=None, real_time=False): try: - if service == 'spotify': if fallback: if quality is None: quality = 'FLAC' if fall_quality is None: - fall_quality='HIGH' + fall_quality = 'HIGH' # First attempt: use Deezer's download_trackspo with 'main' (Deezer credentials) try: deezer_creds_dir = os.path.join('./creds/deezer', main) @@ -29,7 +28,8 @@ def download_track(service, url, main, fallback=None, quality=None, fall_quality recursive_quality=False, recursive_download=False, not_interface=False, - method_save=1 + method_save=1, + real_time_dl=real_time ) except Exception as e: spo_creds_dir = os.path.join('./creds/spotify', fallback) @@ -42,12 +42,13 @@ def download_track(service, url, main, fallback=None, quality=None, fall_quality recursive_quality=False, recursive_download=False, not_interface=False, - method_save=1 + method_save=1, + real_time_dl=real_time ) else: # Directly use Spotify main account if quality is None: - quality='HIGH' + quality = 'HIGH' creds_dir = os.path.join('./creds/spotify', main) credentials_path = os.path.abspath(os.path.join(creds_dir, 'credentials.json')) spo = SpoLogin(credentials_path=credentials_path) @@ -58,12 +59,13 @@ def download_track(service, url, main, fallback=None, quality=None, fall_quality recursive_quality=False, recursive_download=False, not_interface=False, - method_save=1 + method_save=1, + real_time_dl=real_time ) elif service == 'deezer': if quality is None: - quality='FLAC' - # Deezer download logic remains unchanged + quality = 'FLAC' + # Deezer download logic remains unchanged, with real_time_dl passed accordingly creds_dir = os.path.join('./creds/deezer', main) creds_path = os.path.abspath(os.path.join(creds_dir, 'credentials.json')) with open(creds_path, 'r') as f: @@ -77,10 +79,11 @@ def download_track(service, url, main, fallback=None, quality=None, fall_quality quality_download=quality, recursive_quality=False, recursive_download=False, - method_save=1 + method_save=1, + real_time_dl=real_time ) else: raise ValueError(f"Unsupported service: {service}") except Exception as e: traceback.print_exc() - raise \ No newline at end of file + raise diff --git a/static/js/app.js b/static/js/app.js index 2992599..570880e 100755 --- a/static/js/app.js +++ b/static/js/app.js @@ -319,6 +319,12 @@ async function startDownload(url, type, item) { apiUrl += `&quality=${encodeURIComponent(service === 'spotify' ? spotifyQuality : deezerQuality)}`; } + // New: append real_time parameter if Real time downloading is enabled + const realTimeEnabled = document.getElementById('realTimeToggle').checked; + if (realTimeEnabled) { + apiUrl += `&real_time=true`; + } + try { const response = await fetch(apiUrl); const data = await response.json(); @@ -326,7 +332,8 @@ async function startDownload(url, type, item) { } catch (error) { showError('Download failed: ' + error.message); } - } +} + function addToQueue(item, type, prgFile) { const queueId = Date.now().toString() + Math.random().toString(36).substr(2, 9); @@ -673,12 +680,12 @@ function saveConfig() { deezer: document.getElementById('deezerAccountSelect').value, fallback: document.getElementById('fallbackToggle').checked, spotifyQuality: document.getElementById('spotifyQualitySelect').value, - deezerQuality: document.getElementById('deezerQualitySelect').value + deezerQuality: document.getElementById('deezerQualitySelect').value, + realTime: document.getElementById('realTimeToggle').checked // new property }; localStorage.setItem('activeConfig', JSON.stringify(config)); } - function loadConfig() { const saved = JSON.parse(localStorage.getItem('activeConfig')) || {}; @@ -699,7 +706,11 @@ function loadConfig() { const deezerQuality = document.getElementById('deezerQualitySelect'); if (deezerQuality) deezerQuality.value = saved.deezerQuality || 'MP3_128'; -} + + // New: Real time downloading toggle + const realTimeToggle = document.getElementById('realTimeToggle'); + if (realTimeToggle) realTimeToggle.checked = !!saved.realTime; +} function isSpotifyUrl(url) { return url.startsWith('https://open.spotify.com/'); diff --git a/templates/index.html b/templates/index.html index 9ebadec..e3f4f70 100755 --- a/templates/index.html +++ b/templates/index.html @@ -46,6 +46,13 @@ +
+ + +