summaryrefslogtreecommitdiff
path: root/tidal_dl_ng/gui_search.py
diff options
context:
space:
mode:
Diffstat (limited to 'tidal_dl_ng/gui_search.py')
-rw-r--r--tidal_dl_ng/gui_search.py285
1 files changed, 233 insertions, 52 deletions
diff --git a/tidal_dl_ng/gui_search.py b/tidal_dl_ng/gui_search.py
index fb303ad..e9a1db5 100644
--- a/tidal_dl_ng/gui_search.py
+++ b/tidal_dl_ng/gui_search.py
@@ -3,21 +3,24 @@
from typing import TYPE_CHECKING, Any
from tidalapi import Album, Artist, Mix, Playlist, Track, Video
-from tidalapi.media import AudioMode
+from tidalapi import VideoQuality as QualityVideo
+from tidalapi.media import AudioMode, Quality
from tidalapi.playlist import Folder
from tidalapi.session import SearchTypes
+from tidal_dl_ng.constants import QueueDownloadStatus
from tidal_dl_ng.helper.tidal import (
get_tidal_media_id,
get_tidal_media_type,
instantiate_media,
name_builder_artist,
+ name_builder_title,
quality_audio_highest,
search_results_all,
url_ending_clean,
)
from tidal_dl_ng.logger import logger_gui
-from tidal_dl_ng.model.gui_data import ResultItem
+from tidal_dl_ng.model.gui_data import QueueDownloadItem, ResultItem
if TYPE_CHECKING:
from tidal_dl_ng.gui import MainWindow
@@ -36,77 +39,142 @@ class GuiSearchManager:
self.main_window.populate_tree_results(results)
def search(self, query: str, types_media: list[Any]) -> list[ResultItem]:
- """Perform a search and return a list of ResultItems."""
- query_clean = query.strip()
+ """Perform a search and return a list of ResultItems.
+
+ Args:
+ query (str): The search query.
+ types_media (list[Any]): The types of media to search for.
+
+ Returns:
+ list[ResultItem]: The search results.
+ """
+ query_clean: str = query.strip()
+
+ # If a direct link was searched for, skip search and create the object from the link directly.
if "http" in query_clean:
- query_clean = url_ending_clean(query_clean)
+ query_clean: str = url_ending_clean(query_clean)
media_type = get_tidal_media_type(query_clean)
item_id = get_tidal_media_id(query_clean)
+
try:
media = instantiate_media(self.main_window.tidal.session, media_type, item_id)
- result_search = {"direct": [media]}
- except Exception:
+ except:
logger_gui.error(f"Media not found (ID: {item_id}). Maybe it is not available anymore.")
- result_search = {"direct": []}
+
+ media = None
+
+ result_search = {"direct": [media]}
else:
- result_search = search_results_all(
+ result_search: dict[str, list[SearchTypes]] = search_results_all(
session=self.main_window.tidal.session, needle=query_clean, types_media=types_media
)
- result = []
+
+ result: list[ResultItem] = []
+
for _media_type, l_media in result_search.items():
if isinstance(l_media, list):
- result.extend(self.search_result_to_model(l_media))
+ result = result + self.search_result_to_model(l_media)
+
return result
def search_result_to_model(self, items: list[SearchTypes]) -> list[ResultItem]:
- """Convert search results to ResultItem models."""
- return [self._to_result_item(idx, item) for idx, item in enumerate(items) if self._to_result_item(idx, item)]
+ """Convert search results to ResultItem models.
+
+ Args:
+ items (list[SearchTypes]): List of search result items.
+
+ Returns:
+ list[ResultItem]: List of ResultItem models.
+ """
+ result: list[ResultItem] = []
+
+ for idx, item in enumerate(items):
+ result_item = self._to_result_item(idx, item)
+
+ if result_item is not None:
+ result.append(result_item)
+
+ return result
- def _to_result_item(self, idx: int, item: Any) -> ResultItem | None:
- """Helper to convert a single item to ResultItem."""
+ def _to_result_item(self, idx: int, item) -> ResultItem | None:
+ """Helper to convert a single item to ResultItem, or None if not valid.
+
+ Args:
+ idx (int): Index of the item.
+ item: The item to convert.
+
+ Returns:
+ ResultItem | None: The converted ResultItem or None if not valid.
+ """
if not item or (hasattr(item, "available") and not item.available):
return None
+ # Prepare common data
explicit = " 🅴" if isinstance(item, Track | Video | Album) and item.explicit else ""
date_user_added = (
item.user_date_added.strftime("%Y-%m-%d_%H:%M") if getattr(item, "user_date_added", None) else ""
)
date_release = self._get_date_release(item)
+ # Map item types to their conversion methods
type_handlers = {
- Track: self._result_item_from_track,
- Video: self._result_item_from_video,
- Playlist: self._result_item_from_playlist,
- Album: self._result_item_from_album,
- Mix: self._result_item_from_mix,
- Artist: self._result_item_from_artist,
- Folder: self._result_item_from_folder,
+ Track: lambda: self._result_item_from_track(idx, item, explicit, date_user_added, date_release),
+ Video: lambda: self._result_item_from_video(idx, item, explicit, date_user_added, date_release),
+ Playlist: lambda: self._result_item_from_playlist(idx, item, date_user_added, date_release),
+ Album: lambda: self._result_item_from_album(idx, item, explicit, date_user_added, date_release),
+ Mix: lambda: self._result_item_from_mix(idx, item, date_user_added, date_release),
+ Artist: lambda: self._result_item_from_artist(idx, item, date_user_added, date_release),
+ Folder: lambda: self._result_item_from_folder(idx, item, date_user_added),
}
+ # Find and execute the appropriate handler
for item_type, handler in type_handlers.items():
if isinstance(item, item_type):
- return handler(idx, item, explicit, date_user_added, date_release)
+ return handler()
+
return None
- def _get_date_release(self, item: Any) -> str:
- """Get the release date string for an item."""
+ def _get_date_release(self, item) -> str:
+ """Get the release date string for an item.
+
+ Args:
+ item: The item to extract the release date from.
+
+ Returns:
+ str: The formatted release date or empty string.
+ """
if hasattr(item, "album") and item.album and getattr(item.album, "release_date", None):
return item.album.release_date.strftime("%Y-%m-%d_%H:%M")
+
if hasattr(item, "release_date") and item.release_date:
return item.release_date.strftime("%Y-%m-%d_%H:%M")
+
return ""
def _result_item_from_track(
- self, idx: int, item: Track, explicit: str, date_user_added: str, date_release: str
+ self, idx: int, item, explicit: str, date_user_added: str, date_release: str
) -> ResultItem:
- """Create a ResultItem from a Track."""
+ """Create a ResultItem from a Track.
+
+ Args:
+ idx (int): Index of the item.
+ item: The Track item.
+ explicit (str): Explicit tag.
+ date_user_added (str): Date user added.
+ date_release (str): Release date.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
+
final_quality = quality_audio_highest(item)
if hasattr(item, "audio_modes") and AudioMode.dolby_atmos.value in item.audio_modes:
final_quality = f"{final_quality} / Dolby Atmos"
+
return ResultItem(
position=idx,
artist=name_builder_artist(item),
- title=f"{item.name}{explicit}",
+ title=f"{name_builder_title(item)}{explicit}",
album=item.album.name,
duration_sec=item.duration,
obj=item,
@@ -117,13 +185,24 @@ class GuiSearchManager:
)
def _result_item_from_video(
- self, idx: int, item: Video, explicit: str, date_user_added: str, date_release: str
+ self, idx: int, item, explicit: str, date_user_added: str, date_release: str
) -> ResultItem:
- """Create a ResultItem from a Video."""
+ """Create a ResultItem from a Video.
+
+ Args:
+ idx (int): Index of the item.
+ item: The Video item.
+ explicit (str): Explicit tag.
+ date_user_added (str): Date user added.
+ date_release (str): Release date.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
return ResultItem(
position=idx,
artist=name_builder_artist(item),
- title=f"{item.name}{explicit}",
+ title=f"{name_builder_title(item)}{explicit}",
album=item.album.name if item.album else "",
duration_sec=item.duration,
obj=item,
@@ -133,10 +212,18 @@ class GuiSearchManager:
date_release=date_release,
)
- def _result_item_from_playlist(
- self, idx: int, item: Playlist, explicit: str, date_user_added: str, date_release: str
- ) -> ResultItem:
- """Create a ResultItem from a Playlist."""
+ def _result_item_from_playlist(self, idx: int, item, date_user_added: str, date_release: str) -> ResultItem:
+ """Create a ResultItem from a Playlist.
+
+ Args:
+ idx (int): Index of the item.
+ item: The Playlist item.
+ date_user_added (str): Date user added.
+ date_release (str): Release date.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
return ResultItem(
position=idx,
artist=", ".join(artist.name for artist in item.promoted_artists) if item.promoted_artists else "",
@@ -151,9 +238,20 @@ class GuiSearchManager:
)
def _result_item_from_album(
- self, idx: int, item: Album, explicit: str, date_user_added: str, date_release: str
+ self, idx: int, item, explicit: str, date_user_added: str, date_release: str
) -> ResultItem:
- """Create a ResultItem from an Album."""
+ """Create a ResultItem from an Album.
+
+ Args:
+ idx (int): Index of the item.
+ item: The Album item.
+ explicit (str): Explicit tag.
+ date_user_added (str): Date user added.
+ date_release (str): Release date.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
return ResultItem(
position=idx,
artist=name_builder_artist(item),
@@ -167,16 +265,24 @@ class GuiSearchManager:
date_release=date_release,
)
- def _result_item_from_mix(
- self, idx: int, item: Mix, explicit: str, date_user_added: str, date_release: str
- ) -> ResultItem:
- """Create a ResultItem from a Mix."""
+ def _result_item_from_mix(self, idx: int, item, date_user_added: str, date_release: str) -> ResultItem:
+ """Create a ResultItem from a Mix.
+
+ Args:
+ idx (int): Index of the item.
+ item: The Mix item.
+ date_user_added (str): Date user added.
+ date_release (str): Release date.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
return ResultItem(
position=idx,
artist=item.sub_title,
title=item.title,
album="",
- duration_sec=-1,
+ duration_sec=-1, # TODO: Calculate total duration.
obj=item,
quality="",
explicit=False,
@@ -184,10 +290,18 @@ class GuiSearchManager:
date_release=date_release,
)
- def _result_item_from_artist(
- self, idx: int, item: Artist, explicit: str, date_user_added: str, date_release: str
- ) -> ResultItem:
- """Create a ResultItem from an Artist."""
+ def _result_item_from_artist(self, idx: int, item, date_user_added: str, date_release: str) -> ResultItem:
+ """Create a ResultItem from an Artist.
+
+ Args:
+ idx (int): Index of the item.
+ item: The Artist item.
+ date_user_added (str): Date user added.
+ date_release (str): Release date.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
return ResultItem(
position=idx,
artist=item.name,
@@ -201,11 +315,18 @@ class GuiSearchManager:
date_release=date_release,
)
- def _result_item_from_folder(
- self, idx: int, item: Folder, explicit: str, date_user_added: str, date_release: str
- ) -> ResultItem:
- """Create a ResultItem from a Folder."""
- total_items = item.total_number_of_items if hasattr(item, "total_number_of_items") else 0
+ def _result_item_from_folder(self, idx: int, item: Folder, date_user_added: str) -> ResultItem:
+ """Create a ResultItem from a Folder.
+
+ Args:
+ idx (int): Index of the item.
+ item (Folder): The Folder item.
+ date_user_added (str): Date user added.
+
+ Returns:
+ ResultItem: The constructed ResultItem.
+ """
+ total_items: int = item.total_number_of_items if hasattr(item, "total_number_of_items") else 0
return ResultItem(
position=idx,
artist="",
@@ -216,5 +337,65 @@ class GuiSearchManager:
quality="",
explicit=False,
date_user_added=date_user_added,
- date_release=date_release,
+ date_release="",
)
+
+ def media_to_queue_download_model(
+ self, media: Artist | Track | Video | Album | Playlist | Mix
+ ) -> QueueDownloadItem | bool:
+ """Convert a media object to a QueueDownloadItem for the download queue.
+
+ Args:
+ media (Artist | Track | Video | Album | Playlist | Mix): The media object.
+
+ Returns:
+ QueueDownloadItem | bool: The queue item or False if not available.
+ """
+ result: QueueDownloadItem | False
+ name: str = ""
+ quality_audio: Quality = self.main_window.settings.data.quality_audio
+ quality_video: QualityVideo = self.main_window.settings.data.quality_video
+ explicit: str = ""
+
+ # Check if item is available on TIDAL.
+ # Note: Some albums have available=None, which should be treated as available
+ if hasattr(media, "available") and media.available is False:
+ return False
+
+ # Set "Explicit" tag
+ if isinstance(media, Track | Video | Album):
+ explicit = " 🅴" if media.explicit else ""
+
+ # Build name and set quality
+ if isinstance(media, Track | Video):
+ name = f"{name_builder_artist(media)} - {name_builder_title(media)}{explicit}"
+ elif isinstance(media, Playlist | Artist):
+ name = media.name
+ elif isinstance(media, Album):
+ name = f"{name_builder_artist(media)} - {media.name}{explicit}"
+ elif isinstance(media, Mix):
+ name = media.title
+
+ # Determine actual quality.
+ if isinstance(media, Track | Album):
+ quality_highest: Quality = quality_audio_highest(media)
+
+ if (
+ self.main_window.settings.data.quality_audio == quality_highest
+ or self.main_window.settings.data.quality_audio == Quality.hi_res_lossless
+ ):
+ quality_audio = quality_highest
+
+ if name:
+ result = QueueDownloadItem(
+ name=name,
+ quality_audio=quality_audio,
+ quality_video=quality_video,
+ type_media=type(media).__name__,
+ status=QueueDownloadStatus.Waiting,
+ obj=media,
+ )
+ else:
+ result = False
+
+ return result