summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug.yml50
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml1
-rw-r--r--.github/ISSUE_TEMPLATE/feature.yml27
-rw-r--r--.github/ISSUE_TEMPLATE/help.yml19
-rw-r--r--pyproject.toml2
-rw-r--r--tidal_dl_ng/download.py47
-rw-r--r--tidal_dl_ng/gui.py106
-rw-r--r--tidal_dl_ng/helper/tidal.py22
-rw-r--r--tidal_dl_ng/model/cfg.py4
9 files changed, 218 insertions, 60 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 0000000..4042edd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,50 @@
+name: Bug Report
+description: File a bug report
+title: "[Bug] "
+labels: ["bug"]
+assignees:
+ - exislow
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Thanks for taking the time to fill out this bug report! You must use a clear title for this bug.
+ - type: textarea
+ id: what-happened
+ attributes:
+ label: What happened?
+ description: Also tell us, what did you expect to happen?
+ placeholder: Use three sentences or more. Otherwise your report gets rejected. Provide all necessary information to be able to reproduce this bug, for instance, links and exact names to your items to tried to download, images of the error.
+ validations:
+ required: true
+ - type: input
+ id: version
+ attributes:
+ label: Version
+ description: What version of our software are you running?
+ placeholder: vX.Y.Z
+ validations:
+ required: true
+ - type: dropdown
+ id: os
+ attributes:
+ label: What operating system are you seeing the problem on?
+ multiple: false
+ options:
+ - macOS
+ - Linux
+ - Windows
+ - type: textarea
+ id: logs
+ attributes:
+ label: Relevant log output
+ description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
+ render: shell
+ - type: textarea
+ id: config
+ attributes:
+ label: Your settings
+ description: Please copy and paste your settings from `~/.config/tidal_dl_ng/settings.json` (macOS & Linux) / `%HOMEPATH%\.config\tidal_dl_ng\settings.json` (Windows). This will be automatically formatted into code, so no need for backticks.
+ render: json
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..3ba13e0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1 @@
+blank_issues_enabled: false
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 0000000..9352708
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,27 @@
+name: Feature Request
+description: File a feature request
+title: "[Feature] "
+labels: ["enhancement"]
+assignees:
+ - exislow
+body:
+ - type: markdown
+ attributes:
+ value: |
+ We appreciate your feedback on how to improve this project. Please be sure to include as much details & any images if possible!
+ - type: textarea
+ id: situation
+ attributes:
+ label: Current Situation
+ description: Describe the current situation, which is lacking of something you like to have.
+ placeholder: Write at least three sentences and be as precise as possible.
+ validations:
+ required: true
+ - type: textarea
+ id: suggestion
+ attributes:
+ label: Suggestion / Feature Request
+ description: Describe the feature(s) you would like to see added.
+ placeholder: Write three sentences or more and be as precise as possible.
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/help.yml b/.github/ISSUE_TEMPLATE/help.yml
new file mode 100644
index 0000000..9a64c7c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/help.yml
@@ -0,0 +1,19 @@
+name: Help
+description: File a question for help
+title: "[Help] "
+labels: ["help wanted"]
+assignees:
+ - exislow
+body:
+ - type: markdown
+ attributes:
+ value: |
+ If no other issue template fits please use this.
+ - type: textarea
+ id: help
+ attributes:
+ label: I need Help.
+ description: Describe the current situation and state your question.
+ placeholder: Write at least three sentences and be as precise as possible.
+ validations:
+ required: true
diff --git a/pyproject.toml b/pyproject.toml
index fce798b..3e4013f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[tool.poetry]
name = "tidal-dl-ng"
authors = ["Robert Honz <cen.sored@gmail.com>"]
-version = "0.8.2"
+version = "0.8.3"
description = "TIDAL Medial Downloader Next Generation!"
repository = "https://github.com/exislow/tidal-dl-ng"
documentation = "https://exislow.github.io/tidal-dl-ng/"
diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py
index 82e1bee..4d752cb 100644
--- a/tidal_dl_ng/download.py
+++ b/tidal_dl_ng/download.py
@@ -28,9 +28,9 @@ from tidal_dl_ng.constants import (
VideoExtensions,
)
from tidal_dl_ng.helper.decryption import decrypt_file, decrypt_security_token
-from tidal_dl_ng.helper.exceptions import MediaMissing, MediaUnknown, UnknownManifestFormat
+from tidal_dl_ng.helper.exceptions import MediaMissing, UnknownManifestFormat
from tidal_dl_ng.helper.path import check_file_exists, format_path_media, path_file_sanitize
-from tidal_dl_ng.helper.tidal import items_results_all, name_builder_item
+from tidal_dl_ng.helper.tidal import instantiate_media, items_results_all, name_builder_item
from tidal_dl_ng.metadata import Metadata
from tidal_dl_ng.model.gui_data import ProgressBars
from tidal_dl_ng.model.tidal import StreamManifest
@@ -154,26 +154,6 @@ class Download:
return tmp_path_file_decrypted
- def instantiate_media(
- self,
- media_type: type[MediaType.TRACK, MediaType.VIDEO, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX],
- id_media: str,
- ) -> Track | Video | Album | Playlist | Mix:
- if media_type == MediaType.TRACK:
- media = self.session.track(id_media, with_album=True)
- elif media_type == MediaType.VIDEO:
- media = self.session.video(id_media)
- elif media_type == MediaType.ALBUM:
- media = self.session.album(id_media)
- elif media_type == MediaType.PLAYLIST:
- media = self.session.playlist(id_media)
- elif media_type == MediaType.MIX:
- media = self.session.mix(id_media)
- else:
- raise MediaUnknown
-
- return media
-
def item(
self,
file_template: str,
@@ -185,14 +165,22 @@ class Download:
) -> (bool, str):
# If no media instance is provided, we need to create the media instance.
if media_id and media_type:
- media = self.instantiate_media(media_type, media_id)
+ media = instantiate_media(self.session, media_type, media_id)
elif isinstance(media, Track):
media = self.session.track(media.id, with_album=True)
elif not media:
raise MediaMissing
+ # Check if media is available not deactivated / removed from TIDAL.
+ if not media.available:
+ self.fn_logger.info(
+ f"This track is not available for listening anymore on TIDAL. Skipping: {name_builder_item(media)}"
+ )
+
+ return False, ""
+
# If video download is not allowed end here
- if not video_download:
+ if not video_download and isinstance(media, Video):
self.fn_logger.info(
f"Video downloads are deactivated (see settings). Skipping video: {name_builder_item(media)}"
)
@@ -297,9 +285,14 @@ class Download:
if self.settings.data.lyrics_embed or self.settings.data.lyrics_file:
# Try to retrieve lyrics.
try:
- lyrics: str = track.lyrics().subtitles
+ lyrics_obj = track.lyrics()
+
+ if lyrics_obj.subtitles:
+ lyrics = lyrics_obj.subtitles
+ elif lyrics_obj.text:
+ lyrics = lyrics_obj.text
except (HTTPError, AttributeError):
- lyrics: str = ""
+ lyrics = ""
# TODO: Implement proper logging.
print(f"Could not retrieve lyrics for `{name_builder_item(track)}`.")
@@ -343,7 +336,7 @@ class Download:
):
# If no media instance is provided, we need to create the media instance.
if media_id and media_type:
- media = self.instantiate_media(media_type, media_id)
+ media = instantiate_media(self.session, media_type, media_id)
elif not media:
raise MediaMissing
diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py
index 6afbe33..0c8e97a 100644
--- a/tidal_dl_ng/gui.py
+++ b/tidal_dl_ng/gui.py
@@ -6,8 +6,16 @@ from requests.exceptions import HTTPError
from tidal_dl_ng import __version__
from tidal_dl_ng.dialog import DialogLogin, DialogPreferences, DialogVersion
+from tidal_dl_ng.helper.exceptions import MediaUnknown
from tidal_dl_ng.helper.path import get_format_template
-from tidal_dl_ng.helper.tidal import items_results_all, search_results_all, user_media_lists
+from tidal_dl_ng.helper.tidal import (
+ get_tidal_media_id,
+ get_tidal_media_type,
+ instantiate_media,
+ items_results_all,
+ search_results_all,
+ user_media_lists,
+)
try:
import qdarktheme
@@ -114,25 +122,27 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
sys.exit(1)
if result:
- # Init `Download` object.
- data_pb: ProgressBars = ProgressBars(
- item=self.s_item_advance,
- list_item=self.s_list_advance,
- item_name=self.s_item_name,
- list_name=self.s_list_name,
- )
- progress: Progress = Progress()
- self.dl = Download(
- session=self.tidal.session,
- skip_existing=self.tidal.settings.data.skip_existing,
- path_base=self.settings.data.download_base_path,
- fn_logger=logger_gui,
- progress_gui=data_pb,
- progress=progress,
- )
-
+ self._init_dl()
self.thread_it(self.tidal_user_lists)
+ def _init_dl(self):
+ # Init `Download` object.
+ data_pb: ProgressBars = ProgressBars(
+ item=self.s_item_advance,
+ list_item=self.s_list_advance,
+ item_name=self.s_item_name,
+ list_name=self.s_list_name,
+ )
+ progress: Progress = Progress()
+ self.dl = Download(
+ session=self.tidal.session,
+ skip_existing=self.tidal.settings.data.skip_existing,
+ path_base=self.settings.data.download_base_path,
+ fn_logger=logger_gui,
+ progress_gui=data_pb,
+ progress=progress,
+ )
+
def _init_progressbar(self):
self.pb_list = QtWidgets.QProgressBar()
self.pb_item = QtWidgets.QProgressBar()
@@ -185,8 +195,6 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.s_populate_tree_lists.emit(user_all)
def on_populate_tree_lists(self, user_lists: [Playlist | UserPlaylist | Mix]):
- self.tr_results.clear()
-
twi_playlists: QtWidgets.QTreeWidgetItem = self.tr_lists_user.findItems(
TidalLists.PLAYLISTS.value, QtCore.Qt.MatchExactly, 0
)[0]
@@ -311,6 +319,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
self.s_tr_results_add_top_level_item.emit(child)
def populate_tree_result_child(self, item: [Track | Video | Mix | Album | Playlist], index_count_digits: int):
+ # TODO: Duration needs to be calculated later to properly fill with zeros.
# Format seconds to mm:ss.
m, s = divmod(item.duration_sec, 60)
duration: str = f"{m:02d}:{s:02d}"
@@ -318,7 +327,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
index: str = str(item.position + 1).zfill(index_count_digits)
# Populate child
- child = QtWidgets.QTreeWidgetItem()
+ child: QtWidgets.QTreeWidgetItem = QtWidgets.QTreeWidgetItem()
child.setText(0, index)
child.setText(1, item.artist)
child.setText(2, item.title)
@@ -327,8 +336,11 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
child.setData(5, QtCore.Qt.ItemDataRole.UserRole, item.obj)
if isinstance(item.obj, Mix | Playlist | Album):
- # Add empty dummy child, so expansion arrow will appear. This Child will be replaced on expansion.
- child.addChild(QtWidgets.QTreeWidgetItem())
+ # Add a disabled dummy child, so expansion arrow will appear. This Child will be replaced on expansion.
+ child_dummy: QtWidgets.QTreeWidgetItem = QtWidgets.QTreeWidgetItem()
+
+ child_dummy.setDisabled(True)
+ child.addChild(child_dummy)
return child
@@ -338,11 +350,25 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def on_settings_save(self):
self.settings.save()
self.apply_settings(self.settings)
+ self._init_dl()
def search(self, query: str, types_media: SearchTypes) -> [ResultItem]:
- result_search: dict[str, [SearchTypes]] = search_results_all(
- session=self.tidal.session, needle=query, types_media=types_media
- )
+ # If a direct link was searched for, skip search and create the object from the link directly.
+ if "http" in query:
+ media_type = get_tidal_media_type(query)
+ item_id = get_tidal_media_id(query)
+
+ try:
+ media = instantiate_media(self.tidal.session, media_type, item_id)
+ except MediaUnknown:
+ media = None
+
+ result_search = {"direct": [media]}
+ else:
+ result_search: dict[str, [SearchTypes]] = search_results_all(
+ session=self.tidal.session, needle=query, types_media=types_media
+ )
+
result: [ResultItem] = []
for _media_type, l_media in result_search.items():
@@ -355,6 +381,10 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
result = []
for idx, item in enumerate(items):
+ # Check if item is available on TIDAL.
+ if hasattr(item, "available") and not item.available:
+ continue
+
if isinstance(item, Track):
result_item: ResultItem = ResultItem(
position=idx,
@@ -399,6 +429,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
)
result.append(result_item)
+ elif isinstance(item, Mix):
+ result_item: ResultItem = ResultItem(
+ position=idx,
+ artist=item.sub_title,
+ title=item.title,
+ album="",
+ # TODO: Calculate total duration.
+ duration_sec=-1,
+ obj=item,
+ )
+
+ result.append(result_item)
return result
@@ -547,11 +589,15 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def on_preferences(self) -> None:
DialogPreferences(settings=self.settings, settings_save=self.s_settings_save, parent=self)
- def on_tr_results_expanded(self, child: QtWidgets.QTreeWidgetItem) -> None:
- child.removeChild(child.child(0))
- media_list: [Mix | Album | Playlist] = child.data(5, QtCore.Qt.ItemDataRole.UserRole)
+ def on_tr_results_expanded(self, list_item: QtWidgets.QTreeWidgetItem) -> None:
+ # If the child is a dummy the list_item has not been expanded before
+ load_children: bool = list_item.child(0).isDisabled()
+
+ if load_children:
+ list_item.removeChild(list_item.child(0))
+ media_list: [Mix | Album | Playlist] = list_item.data(5, QtCore.Qt.ItemDataRole.UserRole)
- self.list_items_show_result(media_list=media_list, parent=child)
+ self.list_items_show_result(media_list=media_list, parent=list_item)
def button_reload_status(self, status: bool):
button_text: str = "Reloading..."
diff --git a/tidal_dl_ng/helper/tidal.py b/tidal_dl_ng/helper/tidal.py
index 9c4eeba..4ae57bd 100644
--- a/tidal_dl_ng/helper/tidal.py
+++ b/tidal_dl_ng/helper/tidal.py
@@ -2,6 +2,7 @@ from tidalapi import Album, Mix, Playlist, Session, Track, UserPlaylist, Video
from tidalapi.session import SearchTypes
from tidal_dl_ng.constants import MediaType
+from tidal_dl_ng.helper.exceptions import MediaUnknown
def name_builder_artist(media: Track) -> str:
@@ -104,3 +105,24 @@ def user_media_lists(session: Session) -> [Playlist | UserPlaylist | Mix]:
result: [Playlist | UserPlaylist | Mix] = user_playlists + user_mixes
return result
+
+
+def instantiate_media(
+ session: Session,
+ media_type: type[MediaType.TRACK, MediaType.VIDEO, MediaType.ALBUM, MediaType.PLAYLIST, MediaType.MIX],
+ id_media: str,
+) -> Track | Video | Album | Playlist | Mix:
+ if media_type == MediaType.TRACK:
+ media = session.track(id_media, with_album=True)
+ elif media_type == MediaType.VIDEO:
+ media = session.video(id_media)
+ elif media_type == MediaType.ALBUM:
+ media = session.album(id_media)
+ elif media_type == MediaType.PLAYLIST:
+ media = session.playlist(id_media)
+ elif media_type == MediaType.MIX:
+ media = session.mix(id_media)
+ else:
+ raise MediaUnknown
+
+ return media
diff --git a/tidal_dl_ng/model/cfg.py b/tidal_dl_ng/model/cfg.py
index 07eda54..fbfd21a 100644
--- a/tidal_dl_ng/model/cfg.py
+++ b/tidal_dl_ng/model/cfg.py
@@ -43,8 +43,8 @@ class HelpSettings:
"different file extension exists."
)
album_cover_save: str = "Safe cover to album folder."
- lyrics_embed: str = "Embed lyrics in audio file."
- lyrics_file: str = "Save lyrics to separate *.lrc file."
+ lyrics_embed: str = "Embed lyrics in audio file, if lyrics are available."
+ lyrics_file: str = "Save lyrics to separate *.lrc file, if lyrics are available."
api_key_index: str = "Set the device API KEY."
album_info_save: str = "Save album info to track?"
video_download: str = "Allow download of videos."