diff --git a/routes/content/album.py b/routes/content/album.py index 52abdf5..ba797d2 100755 --- a/routes/content/album.py +++ b/routes/content/album.py @@ -74,6 +74,7 @@ async def handle_download(album_id: str, request: Request, current_user: User = "url": url, "name": name_from_spotify, "artist": artist_from_spotify, + "username": current_user.username, "orig_request": orig_params, } ) diff --git a/routes/content/playlist.py b/routes/content/playlist.py index fd359b6..066fafe 100755 --- a/routes/content/playlist.py +++ b/routes/content/playlist.py @@ -96,6 +96,7 @@ async def handle_download(playlist_id: str, request: Request, current_user: User "url": url, "name": name_from_spotify, # Use fetched name "artist": artist_from_spotify, # Use fetched owner name as artist + "username": current_user.username, "orig_request": orig_params, } ) diff --git a/routes/content/track.py b/routes/content/track.py index 835e4f8..b3d6d3c 100755 --- a/routes/content/track.py +++ b/routes/content/track.py @@ -74,6 +74,7 @@ async def handle_download(track_id: str, request: Request, current_user: User = "url": url, "name": name_from_spotify, "artist": artist_from_spotify, + "username": current_user.username, "orig_request": orig_params, } ) diff --git a/routes/utils/celery_config.py b/routes/utils/celery_config.py index 6e06bcd..5eeb64a 100644 --- a/routes/utils/celery_config.py +++ b/routes/utils/celery_config.py @@ -47,6 +47,7 @@ DEFAULT_MAIN_CONFIG = { "artistSeparator": "; ", "recursiveQuality": False, "spotifyMetadata": True, + "separateTracksByUser": False, "watch": {}, } diff --git a/routes/utils/celery_queue_manager.py b/routes/utils/celery_queue_manager.py index 0734261..d067a27 100644 --- a/routes/utils/celery_queue_manager.py +++ b/routes/utils/celery_queue_manager.py @@ -70,6 +70,7 @@ def get_config_params(): "recursiveQuality": config.get( "recursiveQuality", config.get("recursive_quality", False) ), + "separateTracksByUser": config.get("separateTracksByUser", False), "watch": config.get("watch", {}), } except Exception as e: @@ -93,6 +94,7 @@ def get_config_params(): "bitrate": None, # Default for bitrate "artistSeparator": "; ", "recursiveQuality": False, + "separateTracksByUser": False, "watch": {}, } @@ -361,6 +363,9 @@ class CeleryDownloadQueueManager: original_request = task.get( "orig_request", task.get("original_request", {}) ) + + # Get username for user-specific paths + username = task.get("username", "") complete_task = { "download_type": incoming_type, @@ -383,8 +388,10 @@ class CeleryDownloadQueueManager: "real_time": self._parse_bool_param( original_request.get("real_time"), config_params["realTime"] ), - "custom_dir_format": original_request.get( - "custom_dir_format", config_params["customDirFormat"] + "custom_dir_format": self._get_user_specific_dir_format( + original_request.get("custom_dir_format", config_params["customDirFormat"]), + config_params.get("separateTracksByUser", False), + username ), "custom_track_format": original_request.get( "custom_track_format", config_params["customTrackFormat"] @@ -487,6 +494,23 @@ class CeleryDownloadQueueManager: return param_value.lower() in ["true", "1", "yes", "y", "on"] return bool(param_value) + def _get_user_specific_dir_format(self, base_format, separate_by_user, username): + """ + Modify the directory format to include username if separateTracksByUser is enabled + + Args: + base_format (str): The base directory format from config + separate_by_user (bool): Whether to separate tracks by user + username (str): The username to include in path + + Returns: + str: The modified directory format + """ + if separate_by_user and username: + # Add username as a subdirectory at the beginning + return f"{username}/{base_format}" + return base_format + def cancel_task(self, task_id): """ Cancels a task by its ID. diff --git a/spotizerr-ui/src/components/config/DownloadsTab.tsx b/spotizerr-ui/src/components/config/DownloadsTab.tsx index f99d54e..e320c39 100644 --- a/spotizerr-ui/src/components/config/DownloadsTab.tsx +++ b/spotizerr-ui/src/components/config/DownloadsTab.tsx @@ -22,6 +22,7 @@ interface DownloadSettings { deezerQuality: "MP3_128" | "MP3_320" | "FLAC"; spotifyQuality: "NORMAL" | "HIGH" | "VERY_HIGH"; recursiveQuality: boolean; // frontend field (sent as camelCase to backend) + separateTracksByUser: boolean; } interface WatchConfig { @@ -195,6 +196,13 @@ export function DownloadsTab({ config, isLoading }: DownloadsTabProps) { +
+ When enabled, downloads will be organized in user-specific subdirectories (downloads/username/...) +
{/* Watch validation info */} {watchConfig?.enabled && ( diff --git a/spotizerr-ui/src/contexts/SettingsProvider.tsx b/spotizerr-ui/src/contexts/SettingsProvider.tsx index 79c1f4e..127f5e8 100644 --- a/spotizerr-ui/src/contexts/SettingsProvider.tsx +++ b/spotizerr-ui/src/contexts/SettingsProvider.tsx @@ -54,6 +54,7 @@ export type FlatAppSettings = { hlsThreads: number; // Frontend-only flag used in DownloadsTab recursiveQuality: boolean; + separateTracksByUser: boolean; // Add defaults for the new formatting properties track: string; album: string; @@ -90,6 +91,7 @@ const defaultSettings: FlatAppSettings = { hlsThreads: 8, // Frontend-only default recursiveQuality: false, + separateTracksByUser: false, // Add defaults for the new formatting properties track: "{artist_name}/{album_name}/{track_number} - {track_name}", album: "{artist_name}/{album_name}", diff --git a/spotizerr-ui/src/contexts/settings-context.ts b/spotizerr-ui/src/contexts/settings-context.ts index 1f0c800..e0c6011 100644 --- a/spotizerr-ui/src/contexts/settings-context.ts +++ b/spotizerr-ui/src/contexts/settings-context.ts @@ -27,6 +27,7 @@ export interface AppSettings { m3u: boolean; hlsThreads: number; recursiveQuality: boolean; + separateTracksByUser: boolean; // Properties from the old 'formatting' object track: string; album: string;