From 2a822c5a9f68d67df575d32df41db3ef4e1edac9 Mon Sep 17 00:00:00 2001 From: "cool.gitter.choco" Date: Thu, 6 Feb 2025 14:14:54 -0600 Subject: [PATCH] added endpoints for custom dir and track format --- routes/album.py | 25 ++++++++++++++++++++++--- routes/artist.py | 26 ++++++++++++++++++++++++-- routes/playlist.py | 25 ++++++++++++++++++------- routes/utils/album.py | 30 ++++++++++++++++++++++++------ routes/utils/artist.py | 22 ++++++++++++++++------ routes/utils/playlist.py | 32 +++++++++++++++++++++++++------- 6 files changed, 129 insertions(+), 31 deletions(-) diff --git a/routes/album.py b/routes/album.py index c143374..27e1e7b 100755 --- a/routes/album.py +++ b/routes/album.py @@ -40,7 +40,8 @@ class FlushingFileWrapper: def flush(self): self.file.flush() -def download_task(service, url, main, fallback, quality, fall_quality, real_time, prg_path, orig_request): +def download_task(service, url, main, fallback, quality, fall_quality, real_time, prg_path, orig_request, + custom_dir_format, custom_track_format): """ The download task writes out the original request data into the progress file and then runs the album download. @@ -69,7 +70,9 @@ def download_task(service, url, main, fallback, quality, fall_quality, real_time fallback=fallback, quality=quality, fall_quality=fall_quality, - real_time=real_time + real_time=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) flushing_file.write(json.dumps({"status": "complete"}) + "\n") except Exception as e: @@ -103,6 +106,10 @@ def handle_download(): real_time_arg = request.args.get('real_time', 'false') real_time = real_time_arg.lower() in ['true', '1', 'yes'] + # New custom formatting parameters (with defaults) + custom_dir_format = request.args.get('custom_dir_format', "%ar_album%/%album%") + custom_track_format = request.args.get('custom_track_format', "%tracknum%. %music% - %artist%") + # Sanitize main and fallback to prevent directory traversal if main: main = os.path.basename(main) @@ -177,7 +184,19 @@ def handle_download(): # Create and start the download process, and track it in the global dictionary. process = Process( target=download_task, - args=(service, url, main, fallback, quality, fall_quality, real_time, prg_path, orig_request) + args=( + service, + url, + main, + fallback, + quality, + fall_quality, + real_time, + prg_path, + orig_request, + custom_dir_format, + custom_track_format + ) ) process.start() download_processes[filename] = process diff --git a/routes/artist.py b/routes/artist.py index 25443b3..a083ee5 100644 --- a/routes/artist.py +++ b/routes/artist.py @@ -44,7 +44,8 @@ class FlushingFileWrapper: def flush(self): self.file.flush() -def download_artist_task(service, artist_url, main, fallback, quality, fall_quality, real_time, album_type, prg_path, orig_request): +def download_artist_task(service, artist_url, main, fallback, quality, fall_quality, real_time, + album_type, prg_path, orig_request, custom_dir_format, custom_track_format): """ This function wraps the call to download_artist_albums, writes the original request data to the progress file, and then writes JSON status updates. @@ -75,6 +76,8 @@ def download_artist_task(service, artist_url, main, fallback, quality, fall_qual fall_quality=fall_quality, real_time=real_time, album_type=album_type, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) flushing_file.write(json.dumps({"status": "complete"}) + "\n") except Exception as e: @@ -108,6 +111,8 @@ def handle_artist_download(): - fall_quality: string (optional, e.g., "HIGH") - real_time: bool (e.g., "true" or "false") - album_type: string(s); one or more of "album", "single", "appears_on", "compilation" (if multiple, comma-separated) + - custom_dir_format: string (optional, default: "%ar_album%/%album%/%copyright%") + - custom_track_format: string (optional, default: "%tracknum%. %music% - %artist%") """ service = request.args.get('service') artist_url = request.args.get('artist_url') @@ -119,6 +124,10 @@ def handle_artist_download(): real_time_arg = request.args.get('real_time', 'false') real_time = real_time_arg.lower() in ['true', '1', 'yes'] + # New query parameters for custom formatting. + custom_dir_format = request.args.get('custom_dir_format', "%ar_album%/%album%") + custom_track_format = request.args.get('custom_track_format', "%tracknum%. %music% - %artist%") + # Sanitize main and fallback to prevent directory traversal if main: main = os.path.basename(main) @@ -195,7 +204,20 @@ def handle_artist_download(): # Create and start the download process. process = Process( target=download_artist_task, - args=(service, artist_url, main, fallback, quality, fall_quality, real_time, album_type, prg_path, orig_request) + args=( + service, + artist_url, + main, + fallback, + quality, + fall_quality, + real_time, + album_type, + prg_path, + orig_request, + custom_dir_format, + custom_track_format + ) ) process.start() download_processes[filename] = process diff --git a/routes/playlist.py b/routes/playlist.py index dc2d578..9b82583 100755 --- a/routes/playlist.py +++ b/routes/playlist.py @@ -1,11 +1,12 @@ -from flask import Blueprint, Response, request -import json import os +import json +import traceback +from deezspot.spotloader import SpoLogin +from deezspot.deezloader import DeeLogin +from multiprocessing import Process import random import string import sys -import traceback -from multiprocessing import Process playlist_bp = Blueprint('playlist', __name__) @@ -40,7 +41,8 @@ class FlushingFileWrapper: def flush(self): self.file.flush() -def download_task(service, url, main, fallback, quality, fall_quality, real_time, prg_path, orig_request): +def download_task(service, url, main, fallback, quality, fall_quality, real_time, + prg_path, orig_request, custom_dir_format, custom_track_format): try: from routes.utils.playlist import download_playlist with open(prg_path, 'w') as f: @@ -65,7 +67,9 @@ def download_task(service, url, main, fallback, quality, fall_quality, real_time fallback=fallback, quality=quality, fall_quality=fall_quality, - real_time=real_time + real_time=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) flushing_file.write(json.dumps({"status": "complete"}) + "\n") except Exception as e: @@ -100,6 +104,10 @@ def handle_download(): real_time_str = request.args.get('real_time', 'false').lower() real_time = real_time_str in ['true', '1', 'yes'] + # New custom formatting parameters, with defaults. + custom_dir_format = request.args.get('custom_dir_format', "%ar_album%/%album%/%copyright%") + custom_track_format = request.args.get('custom_track_format', "%tracknum%. %music% - %artist%") + if not all([service, url, main]): return Response( json.dumps({"error": "Missing parameters"}), @@ -117,7 +125,10 @@ def handle_download(): process = Process( target=download_task, - args=(service, url, main, fallback, quality, fall_quality, real_time, prg_path, orig_request) + args=( + service, url, main, fallback, quality, fall_quality, real_time, + prg_path, orig_request, custom_dir_format, custom_track_format + ) ) process.start() # Track the running process using the generated filename. diff --git a/routes/utils/album.py b/routes/utils/album.py index 6a22111..a64b431 100755 --- a/routes/utils/album.py +++ b/routes/utils/album.py @@ -4,7 +4,17 @@ 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, real_time=False): +def download_album( + service, + url, + main, + fallback=None, + quality=None, + fall_quality=None, + real_time=False, + custom_dir_format="%ar_album%/%album%/%copyright%", + custom_track_format="%tracknum%. %music% - %artist%" +): try: if service == 'spotify': if fallback: @@ -23,7 +33,7 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality dl = DeeLogin( arl=deezer_creds.get('arl', ''), ) - # Download using download_albumspo; pass real_time_dl accordingly + # Download using download_albumspo; pass real_time_dl accordingly and the custom formatting dl.download_albumspo( link_album=url, output_dir="./downloads", @@ -32,7 +42,9 @@ 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, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) except Exception as e: # Load fallback Spotify credentials and attempt download @@ -49,7 +61,9 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality not_interface=False, method_save=1, make_zip=False, - real_time_dl=real_time + real_time_dl=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) except Exception as e2: # If fallback also fails, raise an error indicating both attempts failed @@ -73,7 +87,9 @@ def download_album(service, url, main, fallback=None, quality=None, fall_quality not_interface=False, method_save=1, make_zip=False, - real_time_dl=real_time + real_time_dl=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) elif service == 'deezer': if quality is None: @@ -93,7 +109,9 @@ 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, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) else: raise ValueError(f"Unsupported service: {service}") diff --git a/routes/utils/artist.py b/routes/utils/artist.py index 7636376..c988f10 100644 --- a/routes/utils/artist.py +++ b/routes/utils/artist.py @@ -43,15 +43,17 @@ def get_artist_discography(url, album_type='album,single,compilation,appears_on' def download_artist_albums(service, artist_url, main, fallback=None, quality=None, - fall_quality=None, real_time=False, album_type='album,single,compilation,appears_on'): + fall_quality=None, real_time=False, album_type='album,single,compilation,appears_on', + custom_dir_format="%ar_album%/%album%/%copyright%", + custom_track_format="%tracknum%. %music% - %artist%"): try: discography = get_artist_discography(artist_url, album_type=album_type) except Exception as e: log_json({"status": "error", "message": f"Error retrieving artist discography: {e}"}) raise albums = discography.get('items', []) - # Extract artist name from the first album's artists - artist_name = artist_url # default fallback + # Extract artist name from the first album's artists as fallback. + artist_name = artist_url if albums: first_album = albums[0] artists = first_album.get('artists', []) @@ -68,7 +70,13 @@ def download_artist_albums(service, artist_url, main, fallback=None, quality=Non }) return - log_json({"status": "initializing", "type": "artist", "artist": artist_name, "total_albums": len(albums), "album_type": album_type}) + log_json({ + "status": "initializing", + "type": "artist", + "artist": artist_name, + "total_albums": len(albums), + "album_type": album_type + }) for album in albums: try: @@ -95,7 +103,9 @@ def download_artist_albums(service, artist_url, main, fallback=None, quality=Non fallback=fallback, quality=quality, fall_quality=fall_quality, - real_time=real_time + real_time=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) except Exception as album_error: @@ -113,4 +123,4 @@ def download_artist_albums(service, artist_url, main, fallback=None, quality=Non "type": "artist", "artist": artist_name, "album_type": album_type - }) \ No newline at end of file + }) diff --git a/routes/utils/playlist.py b/routes/utils/playlist.py index 3a7dc2b..659309f 100755 --- a/routes/utils/playlist.py +++ b/routes/utils/playlist.py @@ -4,7 +4,17 @@ 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, real_time=False): +def download_playlist( + service, + url, + main, + fallback=None, + quality=None, + fall_quality=None, + real_time=False, + custom_dir_format="%ar_album%/%album%/%copyright%", + custom_track_format="%tracknum%. %music% - %artist%" +): try: if service == 'spotify': if fallback: @@ -23,7 +33,7 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual dl = DeeLogin( arl=deezer_creds.get('arl', ''), ) - # Download using download_playlistspo + # Download using download_playlistspo; pass the custom formatting parameters. dl.download_playlistspo( link_playlist=url, output_dir="./downloads", @@ -32,7 +42,9 @@ 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, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) except Exception as e: # Load fallback Spotify credentials and attempt download @@ -49,7 +61,9 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual not_interface=False, method_save=1, make_zip=False, - real_time_dl=real_time + real_time_dl=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) except Exception as e2: # If fallback also fails, raise an error indicating both attempts failed @@ -73,12 +87,14 @@ def download_playlist(service, url, main, fallback=None, quality=None, fall_qual not_interface=False, method_save=1, make_zip=False, - real_time_dl=real_time + real_time_dl=real_time, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) elif service == 'deezer': if quality is None: quality = 'FLAC' - # Existing code for Deezer, using main as Deezer account + # 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')) with open(creds_path, 'r') as f: @@ -93,7 +109,9 @@ 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, + custom_dir_format=custom_dir_format, + custom_track_format=custom_track_format ) else: raise ValueError(f"Unsupported service: {service}")