diff --git a/TIDALDL-PY/requirements.txt b/TIDALDL-PY/requirements.txt index 624a9f9..80711bb 100644 --- a/TIDALDL-PY/requirements.txt +++ b/TIDALDL-PY/requirements.txt @@ -9,4 +9,5 @@ lyricsgenius==3.0.1 pydub==0.25.1 PyQt5==5.15.7 qt-material==2.12 -lxml==4.7.1 \ No newline at end of file +lxml==4.7.1 +tidalapi==0.7.3 diff --git a/TIDALDL-PY/tidal_dl/__init__.py b/TIDALDL-PY/tidal_dl/__init__.py index 4959091..c873f6a 100644 --- a/TIDALDL-PY/tidal_dl/__init__.py +++ b/TIDALDL-PY/tidal_dl/__init__.py @@ -6,27 +6,28 @@ @Author : Yaronzz @Version : 3.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : ''' import sys import getopt -from tidal_dl.events import * -from tidal_dl.settings import * -from tidal_dl.gui import startGui +from events import * +from settings import * +from gui import startGui def mainCommand(): try: - opts, args = getopt.getopt(sys.argv[1:], - "hvgl:o:q:r:", + opts, args = getopt.getopt(sys.argv[1:], + "hvgl:o:q:r:", ["help", "version", "gui", "link=", "output=", "quality", "resolution"]) except getopt.GetoptError as errmsg: - Printf.err(vars(errmsg)['msg'] + ". Use 'tidal-dl -h' for useage.") + Printf.err(vars(errmsg)['msg'] + ". Use 'tidal-dl -h' for usage.") return link = None showGui = False + for opt, val in opts: if opt in ('-h', '--help'): Printf.usage() @@ -52,11 +53,11 @@ def mainCommand(): SETTINGS.videoQuality = SETTINGS.getVideoQuality(val) SETTINGS.save() continue - + if not aigpy.path.mkdirs(SETTINGS.downloadPath): Printf.err(LANG.select.MSG_PATH_ERR + SETTINGS.downloadPath) return - + if showGui: startGui() return @@ -71,22 +72,22 @@ def main(): SETTINGS.read(getProfilePath()) TOKEN.read(getTokenPath()) TIDAL_API.apiKey = apiKey.getItem(SETTINGS.apiKeyIndex) - + if len(sys.argv) > 1: mainCommand() return - + Printf.logo() Printf.settings() - + if not apiKey.isItemValid(SETTINGS.apiKeyIndex): changeApiKey() loginByWeb() elif not loginByConfig(): loginByWeb() - + Printf.checkVersion() - + while True: Printf.choices() choice = Printf.enter(LANG.select.PRINT_ENTER_CHOICE) @@ -115,10 +116,10 @@ def main(): def test(): SETTINGS.read(getProfilePath()) TOKEN.read(getTokenPath()) - + if not loginByConfig(): loginByWeb() - + SETTINGS.audioQuality = AudioQuality.Master SETTINGS.videoFileFormat = VideoQuality.P240 SETTINGS.checkExist = False @@ -149,7 +150,7 @@ def test(): # playlist 98235845-13e8-43b4-94e2-d9f8e603cee7 # start('98235845-13e8-43b4-94e2-d9f8e603cee7') # video 155608351 188932980 https://tidal.com/browse/track/55130637 - # start("155608351")https://tidal.com/browse/track/199683732 + # start("155608351")https://tidal.com/browse/track/199683732 if __name__ == '__main__': diff --git a/TIDALDL-PY/tidal_dl/download.py b/TIDALDL-PY/tidal_dl/download.py index 37b00c3..c0f6fa0 100644 --- a/TIDALDL-PY/tidal_dl/download.py +++ b/TIDALDL-PY/tidal_dl/download.py @@ -6,18 +6,16 @@ @Author : Yaronzz @Version : 1.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : ''' -import aigpy -import logging - -from tidal_dl.paths import * -from tidal_dl.printf import * -from tidal_dl.decryption import * -from tidal_dl.tidal import * from concurrent.futures import ThreadPoolExecutor +from decryption import * +from printf import * +from tidal import * + + def __isSkip__(finalpath, url): if not SETTINGS.checkExist: return False @@ -114,7 +112,7 @@ def downloadVideo(video: Video, album: Album = None, playlist: Playlist = None): try: stream = TIDAL_API.getVideoStreamUrl(video.id, SETTINGS.videoQuality) path = getVideoPath(video, album, playlist) - + Printf.video(video, stream) logging.info("[DL Video] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.m3u8Url) @@ -164,7 +162,7 @@ def downloadTrack(track: Track, album=None, playlist=None, userProgress=None, pa tool.setPartSize(partSize) check, err = tool.start(SETTINGS.showProgress and not SETTINGS.multiThread) if not check: - Printf.err(f"DL Track[{track.title}] failed.{str(err)}") + Printf.err(f"DL Track '{track.title}' failed: {str(err)}") return False, str(err) # encrypted -> decrypt and remove encrypted file @@ -187,19 +185,20 @@ def downloadTrack(track: Track, album=None, playlist=None, userProgress=None, pa __setMetaData__(track, album, path, contributors, lyrics) Printf.success(track.title) + return True, '' except Exception as e: - Printf.err(f"DL Track[{track.title}] failed.{str(e)}") + Printf.err(f"DL Track '{track.title}' failed: {str(e)}") return False, str(e) -def downloadTracks(tracks, album: Album = None, playlist : Playlist=None): +def downloadTracks(tracks, album: Album = None, playlist: Playlist = None): def __getAlbum__(item: Track): album = TIDAL_API.getAlbum(item.album.id) if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder: downloadCover(album) return album - + if not SETTINGS.multiThread: for index, item in enumerate(tracks): itemAlbum = album diff --git a/TIDALDL-PY/tidal_dl/events.py b/TIDALDL-PY/tidal_dl/events.py index 9fd521d..0b46332 100644 --- a/TIDALDL-PY/tidal_dl/events.py +++ b/TIDALDL-PY/tidal_dl/events.py @@ -6,17 +6,10 @@ @Author : Yaronzz @Version : 1.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : """ -import aigpy -import time - -from tidal_dl.model import * -from tidal_dl.enums import * -from tidal_dl.tidal import * -from tidal_dl.printf import * -from tidal_dl.download import * +from download import * ''' ================================= diff --git a/TIDALDL-PY/tidal_dl/gui.py b/TIDALDL-PY/tidal_dl/gui.py index fddb6e3..1a2abf0 100644 --- a/TIDALDL-PY/tidal_dl/gui.py +++ b/TIDALDL-PY/tidal_dl/gui.py @@ -6,17 +6,13 @@ @Author : Yaronzz @Version : 1.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : """ -import sys -import aigpy -import _thread import importlib +import sys -from tidal_dl.events import * -from tidal_dl.settings import * -from tidal_dl.printf import * -from tidal_dl.enums import * +from events import * +from printf import * def enableGui(): @@ -33,41 +29,44 @@ if not enableGui(): Printf.err("Not support gui. Please type: `pip3 install PyQt5 qt_material`") else: from PyQt5.QtCore import Qt, QObject - from PyQt5.QtGui import QTextCursor + from PyQt5.QtGui import QTextCursor, QKeyEvent from PyQt5.QtCore import pyqtSignal from PyQt5 import QtWidgets from qt_material import apply_stylesheet + class SettingView(QtWidgets.QWidget): def __init__(self, ) -> None: super().__init__() self.initView() - + def initView(self): self.c_pathDownload = QtWidgets.QLineEdit() self.c_pathAlbumFormat = QtWidgets.QLineEdit() self.c_pathTrackFormat = QtWidgets.QLineEdit() self.c_pathVideoFormat = QtWidgets.QLineEdit() - + self.mainGrid = QtWidgets.QVBoxLayout(self) self.mainGrid.addWidget(self.c_pathDownload) self.mainGrid.addWidget(self.c_pathAlbumFormat) self.mainGrid.addWidget(self.c_pathTrackFormat) self.mainGrid.addWidget(self.c_pathVideoFormat) - + + class EmittingStream(QObject): textWritten = pyqtSignal(str) def write(self, text): self.textWritten.emit(str(text)) - + + class MainView(QtWidgets.QWidget): s_downloadEnd = pyqtSignal(str, bool, str) def __init__(self, ) -> None: super().__init__() self.initView() - self.setMinimumSize(600, 620) + self.setMinimumSize(800, 620) self.setWindowTitle("Tidal-dl") def __info__(self, msg): @@ -95,7 +94,7 @@ else: self.m_supportType = [Type.Album, Type.Playlist, Type.Track, Type.Video, Type.Artist] for item in self.m_supportType: self.c_combType.addItem(item.name, item) - + for item in AudioQuality: self.c_combTQuality.addItem(item.name, item) for item in VideoQuality: @@ -111,7 +110,7 @@ else: self.c_tableInfo.setShowGrid(False) self.c_tableInfo.verticalHeader().setVisible(False) self.c_tableInfo.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self.c_tableInfo.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) + self.c_tableInfo.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.c_tableInfo.horizontalHeader().setStretchLastSection(True) self.c_tableInfo.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents) self.c_tableInfo.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) @@ -120,10 +119,20 @@ else: item = QtWidgets.QTableWidgetItem(name) self.c_tableInfo.setHorizontalHeaderItem(index, item) + # Create Tree View for playlists. + self.tree_playlists = QtWidgets.QTreeWidget() + self.tree_playlists.setAnimated(False) + self.tree_playlists.setIndentation(20) + self.tree_playlists.setSortingEnabled(True) + self.tree_playlists.resize(200, 400) + self.tree_playlists.setColumnCount(2) + self.tree_playlists.setHeaderLabels(("Name", "# Tracks")) + self.tree_playlists.setColumnWidth(0, 200) + # print self.c_printTextEdit = QtWidgets.QTextEdit() self.c_printTextEdit.setReadOnly(True) - self.c_printTextEdit.setFixedHeight(150) + self.c_printTextEdit.setFixedHeight(100) sys.stdout = EmittingStream(textWritten=self.__output__) sys.stderr = EmittingStream(textWritten=self.__output__) @@ -146,18 +155,31 @@ else: self.funcGrid.addWidget(self.c_tableInfo) self.funcGrid.addLayout(self.line2Grid) self.funcGrid.addWidget(self.c_printTextEdit) - + self.mainGrid = QtWidgets.QGridLayout(self) - self.mainGrid.addLayout(self.funcGrid, 0, 0) + self.mainGrid.addWidget(self.tree_playlists, 0, 0, 1, 2) + self.mainGrid.addLayout(self.funcGrid, 0, 2, 1, 3) self.mainGrid.addWidget(self.c_widgetSetting, 0, 0) # connect self.c_btnSearch.clicked.connect(self.search) + self.c_lineSearch.returnPressed.connect(self.search) self.c_btnDownload.clicked.connect(self.download) self.s_downloadEnd.connect(self.downloadEnd) self.c_combTQuality.currentIndexChanged.connect(self.changeTQuality) self.c_combVQuality.currentIndexChanged.connect(self.changeVQuality) self.c_btnSetting.clicked.connect(self.showSettings) + self.tree_playlists.itemClicked.connect(self.playlist_display_tracks) + + # Connect the contextmenu + self.tree_playlists.setContextMenuPolicy(Qt.CustomContextMenu) + self.tree_playlists.customContextMenuRequested.connect(self.menuContextTree) + + def keyPressEvent(self, event: QKeyEvent): + key = event.key() + + if event.modifiers() & Qt.MetaModifier and key == Qt.Key_A: + self.c_tableInfo.selectAll() def addItem(self, rowIdx: int, colIdx: int, text): if isinstance(text, str): @@ -166,9 +188,9 @@ else: def search(self): self.c_tableInfo.setRowCount(0) - self.s_type = self.c_combType.currentData() self.s_text = self.c_lineSearch.text() + if self.s_text.startswith('http'): tmpType, tmpId = TIDAL_API.parseUrl(self.s_text) if tmpType == Type.Null: @@ -194,22 +216,27 @@ else: self.__info__('No result!') return - self.c_tableInfo.setRowCount(len(self.s_array)) - for index, item in enumerate(self.s_array): + self.set_table_search_results(self.s_array, self.s_type) + + def set_table_search_results(self, s_array, s_type): + self.c_tableInfo.clearSelection() + self.c_tableInfo.setRowCount(len(s_array)) + + for index, item in enumerate(s_array): self.addItem(index, 0, str(index + 1)) - if self.s_type in [Type.Album, Type.Track]: + if s_type in [Type.Album, Type.Track]: self.addItem(index, 1, item.title) self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists)) self.addItem(index, 3, item.audioQuality) - elif self.s_type in [Type.Video]: + elif s_type in [Type.Video]: self.addItem(index, 1, item.title) self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists)) self.addItem(index, 3, item.quality) - elif self.s_type in [Type.Playlist]: + elif s_type in [Type.Playlist]: self.addItem(index, 1, item.title) self.addItem(index, 2, '') self.addItem(index, 3, '') - elif self.s_type in [Type.Artist]: + elif s_type in [Type.Artist]: self.addItem(index, 1, item.name) self.addItem(index, 2, '') self.addItem(index, 3, '') @@ -217,58 +244,113 @@ else: def download(self): index = self.c_tableInfo.currentIndex().row() - if index < 0: + selection = self.c_tableInfo.selectionModel() + has_selection = selection.hasSelection() + + if has_selection == False: self.__info__('Please select a row first.') return - self.c_btnDownload.setEnabled(False) - item_to_download = "" - if isinstance(self.s_array[index], Artist): - item_to_download = self.s_array[index].name - else: - item_to_download = self.s_array[index].title - self.c_btnDownload.setText(f"Downloading [${item_to_download}]...") + rows = self.c_tableInfo.selectionModel().selectedRows() - def __thread_download__(model: MainView): - downloading_item = "" - try: - type = model.s_type - item = model.s_array[index] - start_type(type, item) - if isinstance(item, Artist): - downloading_item = item.name - else: - downloading_item = item.title - model.s_downloadEnd.emit(downloading_item, True, '') - except Exception as e: - model.s_downloadEnd.emit(downloading_item, False, str(e)) + for row in rows: + index = row.row() + item = self.s_array[index] + item_type = self.s_type - _thread.start_new_thread(__thread_download__, (self, )) + self.download_item(item, item_type) + + def download_item(self, item, item_type): + self.c_btnDownload.setEnabled(False) + item_to_download = "" + if isinstance(item, Artist): + item_to_download = item.name + else: + item_to_download = item.title + + self.c_btnDownload.setText(f"'{item_to_download}' ...") + self.download_(item, item_type) + + # Not race condition safe. Needs refactoring. + def download_(self, item, s_type): + downloading_item = "" + try: + item_type = s_type + + start_type(item_type, item) + + if isinstance(item, Artist): + downloading_item = item.name + else: + downloading_item = item.title + + self.s_downloadEnd.emit(downloading_item, True, '') + except Exception as e: + self.s_downloadEnd.emit(downloading_item, False, str(e)) def downloadEnd(self, title, result, msg): self.c_btnDownload.setEnabled(True) self.c_btnDownload.setText(f"Download") if result: - self.__info__(f'Download [{title}] finish') + Printf.info(f"Download '{title}' finished.") else: - self.__info__(f'Download [{title}] failed:{msg}') + Printf.err(f"Download '{title}' failed:{msg}") def checkLogin(self): if not loginByConfig(): self.__info__('Login failed. Please log in using the command line first.') - + def changeTQuality(self, index): SETTINGS.audioQuality = self.c_combTQuality.itemData(index) SETTINGS.save() - + def changeVQuality(self, index): SETTINGS.videoQuality = self.c_combVQuality.itemData(index) SETTINGS.save() - + def showSettings(self): self.c_widgetSetting.show() - + + def tree_items_playlists(self): + playlists = TIDAL_API.get_playlists() + + for playlist in playlists: + item = QtWidgets.QTreeWidgetItem(self.tree_playlists) + item.setText(0, playlist.name) + item.setText(1, str(playlist.num_tracks)) + item.setText(2, playlist.id) + + def playlist_display_tracks(self, item, column): + tracks = TIDAL_API.get_playlist_items(item.text(2)) + self.s_array = tracks + self.s_type = Type.Track + + self.set_table_search_results(tracks, Type.Track) + + def menuContextTree(self, point): + # Infos about the node selected. + index = self.tree_playlists.indexAt(point) + + if not index.isValid(): + return + + item = self.tree_playlists.itemAt(point) + playlist = Playlist() + playlist.title = item.text(0) + playlist.uuid = item.text(2) + + # We build the menu. + menu = QtWidgets.QMenu() + action = menu.addAction("Dowload Playlist") + + menu.exec_(self.tree_playlists.mapToGlobal(point)) + + self.download_item(playlist, Type.Playlist) + + + + def startGui(): aigpy.cmd.enableColor(False) @@ -278,10 +360,10 @@ else: window = MainView() window.show() window.checkLogin() + window.tree_items_playlists() app.exec_() - if __name__ == '__main__': SETTINGS.read(getProfilePath()) TOKEN.read(getTokenPath()) diff --git a/TIDALDL-PY/tidal_dl/lang/language.py b/TIDALDL-PY/tidal_dl/lang/language.py index aff7e6b..6bc1644 100644 --- a/TIDALDL-PY/tidal_dl/lang/language.py +++ b/TIDALDL-PY/tidal_dl/lang/language.py @@ -6,31 +6,31 @@ @Author : Yaronzz @Version : 1.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : ''' -from tidal_dl.lang.arabic import LangArabic -from tidal_dl.lang.chinese import LangChinese -from tidal_dl.lang.croatian import LangCroatian -from tidal_dl.lang.czech import LangCzech -from tidal_dl.lang.danish import LangDanish -from tidal_dl.lang.dutch import LangDutch -from tidal_dl.lang.english import LangEnglish -from tidal_dl.lang.filipino import LangFilipino -from tidal_dl.lang.french import LangFrench -from tidal_dl.lang.german import LangGerman -from tidal_dl.lang.hungarian import LangHungarian -from tidal_dl.lang.italian import LangItalian -from tidal_dl.lang.norwegian import LangNorwegian -from tidal_dl.lang.polish import LangPolish -from tidal_dl.lang.portuguese import LangPortuguese -from tidal_dl.lang.russian import LangRussian -from tidal_dl.lang.spanish import LangSpanish -from tidal_dl.lang.turkish import LangTurkish -from tidal_dl.lang.ukrainian import LangUkrainian -from tidal_dl.lang.vietnamese import LangVietnamese -from tidal_dl.lang.korean import LangKorean -from tidal_dl.lang.japanese import LangJapanese +from lang.arabic import LangArabic +from lang.chinese import LangChinese +from lang.croatian import LangCroatian +from lang.czech import LangCzech +from lang.danish import LangDanish +from lang.dutch import LangDutch +from lang.english import LangEnglish +from lang.filipino import LangFilipino +from lang.french import LangFrench +from lang.german import LangGerman +from lang.hungarian import LangHungarian +from lang.italian import LangItalian +from lang.norwegian import LangNorwegian +from lang.polish import LangPolish +from lang.portuguese import LangPortuguese +from lang.russian import LangRussian +from lang.spanish import LangSpanish +from lang.turkish import LangTurkish +from lang.ukrainian import LangUkrainian +from lang.vietnamese import LangVietnamese +from lang.korean import LangKorean +from lang.japanese import LangJapanese _ALL_LANGUAGE_ = [ ['English', LangEnglish()], @@ -66,7 +66,7 @@ class Language(object): return int(str) except: return 0 - + def setLang(self, index): index = self.__toInt__(index) if index >= 0 and index < len(_ALL_LANGUAGE_): diff --git a/TIDALDL-PY/tidal_dl/paths.py b/TIDALDL-PY/tidal_dl/paths.py index 38f2c36..ab88d43 100644 --- a/TIDALDL-PY/tidal_dl/paths.py +++ b/TIDALDL-PY/tidal_dl/paths.py @@ -6,14 +6,14 @@ @Author : Yaronzz @Version : 1.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : """ import os import aigpy import datetime -from tidal_dl.tidal import * -from tidal_dl.settings import * +from tidal import * +from settings import * def __fixPath__(name: str): diff --git a/TIDALDL-PY/tidal_dl/printf.py b/TIDALDL-PY/tidal_dl/printf.py index 00f0796..5349891 100644 --- a/TIDALDL-PY/tidal_dl/printf.py +++ b/TIDALDL-PY/tidal_dl/printf.py @@ -6,7 +6,7 @@ @Author : Yaronzz @Version : 3.0 @Contact : yaronhuang@foxmail.com -@Desc : +@Desc : ''' from pickle import GLOBAL import threading @@ -14,12 +14,12 @@ import aigpy import logging import prettytable -import tidal_dl.apiKey as apiKey +import apiKey as apiKey -from tidal_dl.model import * -from tidal_dl.paths import * -from tidal_dl.settings import * -from tidal_dl.lang.language import * +from model import * +from paths import * +from settings import * +from lang.language import * VERSION = '2022.10.31.1' @@ -32,9 +32,9 @@ __LOGO__ = f''' | $$ | $$| $$ | $$ /$$__ $$| $$ | $$ | $$| $$ | $$ | $$| $$$$$$$| $$$$$$$| $$ | $$$$$$$| $$ |__/ |__/ \_______/ \_______/|__/ \_______/|__/ - - https://github.com/yaronzz/Tidal-Media-Downloader - + + https://github.com/yaronzz/Tidal-Media-Downloader + {VERSION} ''' @@ -56,7 +56,7 @@ class Printf(object): for item in rows: tb.add_row(item) return tb - + @staticmethod def usage(): print("=============TIDAL-DL HELP==============") @@ -70,7 +70,7 @@ class Printf(object): ["-r or --resolution", "video resolution('P1080', 'P720', 'P480', 'P360')"] ]) print(tb) - + @staticmethod def checkVersion(): onlineVer = aigpy.pip.getLastVersion('tidal-dl') @@ -90,11 +90,11 @@ class Printf(object): [LANG.select.SETTING_PLAYLIST_FOLDER_FORMAT, data.playlistFolderFormat], [LANG.select.SETTING_TRACK_FILE_FORMAT, data.trackFileFormat], [LANG.select.SETTING_VIDEO_FILE_FORMAT, data.videoFileFormat], - + #settings - quality [LANG.select.SETTING_AUDIO_QUALITY, data.audioQuality], [LANG.select.SETTING_VIDEO_QUALITY, data.videoQuality], - + #settings - else [LANG.select.SETTING_USE_PLAYLIST_FOLDER, data.usePlaylistFolder], [LANG.select.SETTING_CHECK_EXIST, data.checkExist], @@ -135,7 +135,7 @@ class Printf(object): aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None) ret = input("") return ret - + @staticmethod def enterBool(string): aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None) @@ -180,7 +180,7 @@ class Printf(object): print(aigpy.cmd.red(LANG.select.PRINT_ERR + " ") + string) # logging.error(string) print_mutex.release() - + @staticmethod def info(string): global print_mutex @@ -295,15 +295,15 @@ class Printf(object): def apikeys(items): print("-------------API-KEYS---------------") tb = prettytable.PrettyTable() - tb.field_names = [aigpy.cmd.green('Index'), + tb.field_names = [aigpy.cmd.green('Index'), aigpy.cmd.green('Valid'), - aigpy.cmd.green('Platform'), + aigpy.cmd.green('Platform'), aigpy.cmd.green('Formats'), ] tb.align = 'l' - + for index, item in enumerate(items): - tb.add_row([str(index), + tb.add_row([str(index), aigpy.cmd.green('True') if item["valid"] == "True" else aigpy.cmd.red('False'), - item["platform"], + item["platform"], item["formats"]]) print(tb) diff --git a/TIDALDL-PY/tidal_dl/settings.py b/TIDALDL-PY/tidal_dl/settings.py index 3582d84..595200c 100644 --- a/TIDALDL-PY/tidal_dl/settings.py +++ b/TIDALDL-PY/tidal_dl/settings.py @@ -12,8 +12,8 @@ import json import aigpy import base64 -from tidal_dl.lang.language import * -from tidal_dl.enums import * +from lang.language import * +from enums import * class Settings(aigpy.model.ModelBase): @@ -61,7 +61,7 @@ class Settings(aigpy.model.ModelBase): if item.name == value: return item return VideoQuality.P360 - + def read(self, path): self._path_ = path txt = aigpy.file.getContent(self._path_) @@ -83,7 +83,7 @@ class Settings(aigpy.model.ModelBase): self.videoFileFormat = self.getDefaultPathFormat(Type.Video) if self.apiKeyIndex is None: self.apiKeyIndex = 0 - + LANG.setLang(self.language) def save(self): diff --git a/TIDALDL-PY/tidal_dl/tidal.py b/TIDALDL-PY/tidal_dl/tidal.py index 915872f..394cf4f 100644 --- a/TIDALDL-PY/tidal_dl/tidal.py +++ b/TIDALDL-PY/tidal_dl/tidal.py @@ -8,18 +8,17 @@ @Contact : yaronhuang@foxmail.com @Desc : tidal api ''' -import json import random import re import time -import aigpy -import base64 -import requests +from typing import Union, List from xml.etree import ElementTree -from tidal_dl.model import * -from tidal_dl.enums import * -from tidal_dl.settings import * +import requests +import tidalapi + +from model import * +from settings import * # SSL Warnings | retry number requests.packages.urllib3.disable_warnings() @@ -43,7 +42,8 @@ class TidalAPI(object): if respond.url.find("playbackinfopostpaywall") != -1 and SETTINGS.downloadDelay is not False: # random sleep between 0.5 and 5 seconds and print it sleep_time = random.randint(500, 5000) / 1000 - print(f"Sleeping for {sleep_time} seconds, to mimic human behaviour and prevent too many requests error") + print( + f"Sleeping for {sleep_time} seconds, to mimic human behaviour and prevent too many requests error") time.sleep(sleep_time) if respond.status_code == 429: @@ -109,7 +109,7 @@ class TidalAPI(object): def __post__(self, path, data, auth=None, urlpre='https://auth.tidal.com/v1/oauth2'): for index in range(3): try: - result = requests.post(urlpre+path, data=data, auth=auth, verify=False).json() + result = requests.post(urlpre + path, data=data, auth=auth, verify=False).json() return result except Exception as e: if index == 2: @@ -157,8 +157,14 @@ class TidalAPI(object): def verifyAccessToken(self, accessToken) -> bool: header = {'authorization': 'Bearer {}'.format(accessToken)} result = requests.get('https://api.tidal.com/v1/sessions', headers=header).json() + if 'status' in result and result['status'] != 200: return False + + # Set tidalapi session. + self.session = tidalapi.session.Session() + self.session.load_oauth_session("Bearer", accessToken) + return True def refreshAccessToken(self, refreshToken) -> bool: @@ -188,11 +194,12 @@ class TidalAPI(object): if not aigpy.string.isNull(userid): if str(result['userId']) != str(userid): - raise Exception("User mismatch! Please use your own accesstoken.",) + raise Exception("User mismatch! Please use your own accesstoken.", ) self.key.userId = result['userId'] self.key.countryCode = result['countryCode'] self.key.accessToken = accessToken + return def getAlbum(self, id) -> Album: @@ -233,6 +240,7 @@ class TidalAPI(object): def search(self, text: str, type: Type, offset: int = 0, limit: int = 10) -> SearchResult: typeStr = type.name.upper() + "S" + if type == Type.Null: typeStr = "ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS" @@ -341,7 +349,7 @@ class TidalAPI(object): tracks.append(track_urls) return tracks - + def getStreamUrl(self, id, quality: AudioQuality): squality = "HI_RES" if quality == AudioQuality.Normal: @@ -373,12 +381,12 @@ class TidalAPI(object): ret.trackid = resp.trackid ret.soundQuality = resp.audioQuality ret.codec = aigpy.string.getSub(xmldata, 'codecs="', '"') - ret.encryptionKey = ""#manifest['keyId'] if 'keyId' in manifest else "" + ret.encryptionKey = "" # manifest['keyId'] if 'keyId' in manifest else "" ret.urls = self.parse_mpd(xmldata)[0] if len(ret.urls) > 0: ret.url = ret.urls[0] return ret - + raise Exception("Can't get the streamUrl, type is " + resp.manifestMimeType) def getVideoStreamUrl(self, id, quality: VideoQuality): @@ -474,6 +482,17 @@ class TidalAPI(object): raise Exception("No result.") + def get_playlists(self) -> List[Union["Playlist", "UserPlaylist"]]: + playlists = self.session.user.playlists() + + return playlists + + def get_playlist_items(self, playlist_id: int) -> Union[tidalapi.Playlist, tidalapi.UserPlaylist]: + # tracks = self.session.playlist(playlist_id).items() + tracks, videos = TIDAL_API.getItems(playlist_id, Type.Playlist) + + return tracks + # Singleton TIDAL_API = TidalAPI()