refactor(download): 🔧 Improve media validation and preparation logic

* Enhanced `_validate_and_prepare_media` method to support additional media types: `Album`, `Playlist`, `UserPlaylist`, and `Mix`.
* Updated media validation checks to ensure proper handling of unavailable media.
* Refactored `_validate_media_instance` to streamline media instance creation and validation.
* Removed redundant `_validate_and_prepare_media_collection` method for cleaner code.
This commit is contained in:
Robert Honz
2025-07-03 08:53:44 +02:00
parent bfc0c92d69
commit a96d44170b
+25 -56
View File
@@ -528,10 +528,12 @@ class Download:
tuple[bool, pathlib.Path | str]: (Downloaded, path to file) tuple[bool, pathlib.Path | str]: (Downloaded, path to file)
""" """
# Step 1: Validate and prepare media # Step 1: Validate and prepare media
media = self._validate_media_instance(media, media_id, media_type, video_download) validated_media = self._validate_and_prepare_media(media, media_id, media_type, video_download)
if media is None: if validated_media is None or not isinstance(validated_media, Track | Video):
return False, "" return False, ""
media = validated_media
# Step 2: Create file paths and determine skip logic # Step 2: Create file paths and determine skip logic
path_media_dst, file_extension_dummy, skip_file, skip_download = self._prepare_file_paths_and_skip_logic( path_media_dst, file_extension_dummy, skip_file, skip_download = self._prepare_file_paths_and_skip_logic(
media, file_template, quality_audio, list_position, list_total media, file_template, quality_audio, list_position, list_total
@@ -564,56 +566,56 @@ class Download:
return download_success, path_media_dst return download_success, path_media_dst
def _validate_media_instance( def _validate_and_prepare_media(
self, self,
media: Track | Video | None, media: Track | Video | Album | Playlist | UserPlaylist | Mix | None,
media_id: str | None, media_id: str | None,
media_type: MediaType | None, media_type: MediaType | None,
video_download: bool, video_download: bool = True,
) -> Track | Video | None: ) -> Track | Video | Album | Playlist | UserPlaylist | Mix | None:
"""Validate and prepare media instance for download. """Validate and prepare media instance for download.
Args: Args:
media (Track | Video | None): Media item instance. media (Track | Video | Album | Playlist | UserPlaylist | Mix | None): Media instance.
media_id (str | None): Media ID if creating new instance. media_id (str | None): Media ID if creating new instance.
media_type (MediaType | None): Media type if creating new instance. media_type (MediaType | None): Media type if creating new instance.
video_download (bool): Whether video downloads are allowed. video_download (bool, optional): Whether video downloads are allowed. Defaults to True.
Returns: Returns:
Track | Video | None: Prepared media instance or None if invalid. Track | Video | Album | Playlist | UserPlaylist | Mix | None: Prepared media instance or None if invalid.
""" """
try: try:
if media_id and media_type: if media_id and media_type:
# If no media instance is provided, we need to create the media instance. # If no media instance is provided, we need to create the media instance.
media_instance = instantiate_media(self.session, media_type, media_id) # Throws `tidalapi.exceptions.ObjectNotFound` if item is not available anymore.
media = instantiate_media(self.session, media_type, media_id)
# Filter to only Track or Video types
if isinstance(media_instance, Track | Video):
media = media_instance
else:
return None
elif isinstance(media, Track | Video): elif isinstance(media, Track | Video):
# Check if media is available not deactivated / removed from TIDAL. # Check if media is available not deactivated / removed from TIDAL.
if not media.available: if not media.available:
self.fn_logger.info( self.fn_logger.info(
f"This item is not available for listening anymore on TIDAL. Skipping: {name_builder_item(media)}" f"This item is not available for listening anymore on TIDAL. Skipping: {name_builder_item(media)}"
) )
return None return None
elif isinstance(media, Track): elif isinstance(media, Track):
# Re-create media instance with full album information # Re-create media instance with full album information
media = self.session.track(str(media.id), with_album=True) media = self.session.track(str(media.id), with_album=True)
elif isinstance(media, Album):
# Check if media is available not deactivated / removed from TIDAL.
if not media.available:
self.fn_logger.info(
f"This item is not available for listening anymore on TIDAL. Skipping: {name_builder_title(media)}"
)
return None
elif not media: elif not media:
raise MediaMissing raise MediaMissing
except: except:
return None return None
# If video download is not allowed end here # If video download is not allowed and this is a video, return None
if not video_download and isinstance(media, Video): if not video_download and isinstance(media, Video):
self.fn_logger.info( self.fn_logger.info(
f"Video downloads are deactivated (see settings). Skipping video: {name_builder_item(media)}" f"Video downloads are deactivated (see settings). Skipping video: {name_builder_item(media)}"
) )
return None return None
return media return media
@@ -1241,10 +1243,12 @@ class Download:
quality_video (QualityVideo | None, optional): Video quality. Defaults to None. quality_video (QualityVideo | None, optional): Video quality. Defaults to None.
""" """
# Validate and prepare media collection # Validate and prepare media collection
media = self._validate_and_prepare_media_collection(media, media_id, media_type) validated_media = self._validate_and_prepare_media(media, media_id, media_type, video_download)
if media is None: if validated_media is None or not isinstance(validated_media, Album | Playlist | UserPlaylist | Mix):
return return
media = validated_media
# Set up download context # Set up download context
download_context = self._setup_collection_download_context(media, file_template, video_download) download_context = self._setup_collection_download_context(media, file_template, video_download)
file_name_relative, list_media_name, list_media_name_short, items, progress_stdout = download_context file_name_relative, list_media_name, list_media_name_short, items, progress_stdout = download_context
@@ -1280,41 +1284,6 @@ class Download:
self.fn_logger.info(f"Finished list '{list_media_name}'.") self.fn_logger.info(f"Finished list '{list_media_name}'.")
def _validate_and_prepare_media_collection(
self,
media: Album | Playlist | UserPlaylist | Mix | None,
media_id: str | None,
media_type: MediaType | None,
) -> Album | Playlist | UserPlaylist | Mix | None:
"""Validate and prepare media collection for download.
Args:
media (Album | Playlist | UserPlaylist | Mix | None): Media collection instance.
media_id (str | None): Media ID if creating new instance.
media_type (MediaType | None): Media type if creating new instance.
Returns:
Album | Playlist | UserPlaylist | Mix | None: Prepared media collection or None if invalid.
"""
try:
if media_id and media_type:
# If no media instance is provided, we need to create the media instance.
# Throws `tidalapi.exceptions.ObjectNotFound` if item is not available anymore.
media = instantiate_media(self.session, media_type, media_id)
elif isinstance(media, Album):
# Check if media is available not deactivated / removed from TIDAL.
if not media.available:
self.fn_logger.info(
f"This item is not available for listening anymore on TIDAL. Skipping: {name_builder_title(media)}"
)
return None
elif not media:
raise MediaMissing
except:
return None
return media
def _setup_collection_download_context( def _setup_collection_download_context(
self, self,
media: Album | Playlist | UserPlaylist | Mix, media: Album | Playlist | UserPlaylist | Mix,