added real time downloading

This commit is contained in:
cool.gitter.choco
2025-01-31 19:45:55 -06:00
parent a10e99e788
commit 3a1315cdbc
11 changed files with 149 additions and 49 deletions

View File

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

View File

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

View File

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

View File

@@ -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
raise # Re-raise the exception after logging

View File

@@ -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
raise # Re-raise the exception after logging

View File

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

View File

@@ -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/');

View File

@@ -46,6 +46,13 @@
<span class="slider"></span>
</label>
</div>
<div class="config-item">
<label>Real time downloading:</label>
<label class="switch">
<input type="checkbox" id="realTimeToggle">
<span class="slider"></span>
</label>
</div>
</div>
<!-- Service Tabs -->