mirror of
https://github.com/yaronzz/Tidal-Media-Downloader.git
synced 2026-06-13 04:05:07 +03:00
Merge pull request #1106 from exislow/gui-playlists
[GUI] Show user's playlists and allow to download them.
This commit is contained in:
@@ -9,4 +9,5 @@ lyricsgenius==3.0.1
|
|||||||
pydub==0.25.1
|
pydub==0.25.1
|
||||||
PyQt5==5.15.7
|
PyQt5==5.15.7
|
||||||
qt-material==2.12
|
qt-material==2.12
|
||||||
lxml==4.7.1
|
lxml==4.7.1
|
||||||
|
tidalapi==0.7.3
|
||||||
|
|||||||
@@ -6,27 +6,28 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 3.0
|
@Version : 3.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc :
|
@Desc :
|
||||||
'''
|
'''
|
||||||
import sys
|
import sys
|
||||||
import getopt
|
import getopt
|
||||||
|
|
||||||
from tidal_dl.events import *
|
from events import *
|
||||||
from tidal_dl.settings import *
|
from settings import *
|
||||||
from tidal_dl.gui import startGui
|
from gui import startGui
|
||||||
|
|
||||||
|
|
||||||
def mainCommand():
|
def mainCommand():
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:],
|
opts, args = getopt.getopt(sys.argv[1:],
|
||||||
"hvgl:o:q:r:",
|
"hvgl:o:q:r:",
|
||||||
["help", "version", "gui", "link=", "output=", "quality", "resolution"])
|
["help", "version", "gui", "link=", "output=", "quality", "resolution"])
|
||||||
except getopt.GetoptError as errmsg:
|
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
|
return
|
||||||
|
|
||||||
link = None
|
link = None
|
||||||
showGui = False
|
showGui = False
|
||||||
|
|
||||||
for opt, val in opts:
|
for opt, val in opts:
|
||||||
if opt in ('-h', '--help'):
|
if opt in ('-h', '--help'):
|
||||||
Printf.usage()
|
Printf.usage()
|
||||||
@@ -52,11 +53,11 @@ def mainCommand():
|
|||||||
SETTINGS.videoQuality = SETTINGS.getVideoQuality(val)
|
SETTINGS.videoQuality = SETTINGS.getVideoQuality(val)
|
||||||
SETTINGS.save()
|
SETTINGS.save()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not aigpy.path.mkdirs(SETTINGS.downloadPath):
|
if not aigpy.path.mkdirs(SETTINGS.downloadPath):
|
||||||
Printf.err(LANG.select.MSG_PATH_ERR + SETTINGS.downloadPath)
|
Printf.err(LANG.select.MSG_PATH_ERR + SETTINGS.downloadPath)
|
||||||
return
|
return
|
||||||
|
|
||||||
if showGui:
|
if showGui:
|
||||||
startGui()
|
startGui()
|
||||||
return
|
return
|
||||||
@@ -71,22 +72,22 @@ def main():
|
|||||||
SETTINGS.read(getProfilePath())
|
SETTINGS.read(getProfilePath())
|
||||||
TOKEN.read(getTokenPath())
|
TOKEN.read(getTokenPath())
|
||||||
TIDAL_API.apiKey = apiKey.getItem(SETTINGS.apiKeyIndex)
|
TIDAL_API.apiKey = apiKey.getItem(SETTINGS.apiKeyIndex)
|
||||||
|
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) > 1:
|
||||||
mainCommand()
|
mainCommand()
|
||||||
return
|
return
|
||||||
|
|
||||||
Printf.logo()
|
Printf.logo()
|
||||||
Printf.settings()
|
Printf.settings()
|
||||||
|
|
||||||
if not apiKey.isItemValid(SETTINGS.apiKeyIndex):
|
if not apiKey.isItemValid(SETTINGS.apiKeyIndex):
|
||||||
changeApiKey()
|
changeApiKey()
|
||||||
loginByWeb()
|
loginByWeb()
|
||||||
elif not loginByConfig():
|
elif not loginByConfig():
|
||||||
loginByWeb()
|
loginByWeb()
|
||||||
|
|
||||||
Printf.checkVersion()
|
Printf.checkVersion()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
Printf.choices()
|
Printf.choices()
|
||||||
choice = Printf.enter(LANG.select.PRINT_ENTER_CHOICE)
|
choice = Printf.enter(LANG.select.PRINT_ENTER_CHOICE)
|
||||||
@@ -115,10 +116,10 @@ def main():
|
|||||||
def test():
|
def test():
|
||||||
SETTINGS.read(getProfilePath())
|
SETTINGS.read(getProfilePath())
|
||||||
TOKEN.read(getTokenPath())
|
TOKEN.read(getTokenPath())
|
||||||
|
|
||||||
if not loginByConfig():
|
if not loginByConfig():
|
||||||
loginByWeb()
|
loginByWeb()
|
||||||
|
|
||||||
SETTINGS.audioQuality = AudioQuality.Master
|
SETTINGS.audioQuality = AudioQuality.Master
|
||||||
SETTINGS.videoFileFormat = VideoQuality.P240
|
SETTINGS.videoFileFormat = VideoQuality.P240
|
||||||
SETTINGS.checkExist = False
|
SETTINGS.checkExist = False
|
||||||
@@ -149,7 +150,7 @@ def test():
|
|||||||
# playlist 98235845-13e8-43b4-94e2-d9f8e603cee7
|
# playlist 98235845-13e8-43b4-94e2-d9f8e603cee7
|
||||||
# start('98235845-13e8-43b4-94e2-d9f8e603cee7')
|
# start('98235845-13e8-43b4-94e2-d9f8e603cee7')
|
||||||
# video 155608351 188932980 https://tidal.com/browse/track/55130637
|
# 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__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -6,18 +6,16 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 1.0
|
@Version : 1.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@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 concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
from decryption import *
|
||||||
|
from printf import *
|
||||||
|
from tidal import *
|
||||||
|
|
||||||
|
|
||||||
def __isSkip__(finalpath, url):
|
def __isSkip__(finalpath, url):
|
||||||
if not SETTINGS.checkExist:
|
if not SETTINGS.checkExist:
|
||||||
return False
|
return False
|
||||||
@@ -114,7 +112,7 @@ def downloadVideo(video: Video, album: Album = None, playlist: Playlist = None):
|
|||||||
try:
|
try:
|
||||||
stream = TIDAL_API.getVideoStreamUrl(video.id, SETTINGS.videoQuality)
|
stream = TIDAL_API.getVideoStreamUrl(video.id, SETTINGS.videoQuality)
|
||||||
path = getVideoPath(video, album, playlist)
|
path = getVideoPath(video, album, playlist)
|
||||||
|
|
||||||
Printf.video(video, stream)
|
Printf.video(video, stream)
|
||||||
logging.info("[DL Video] name=" + aigpy.path.getFileName(path) + "\nurl=" + stream.m3u8Url)
|
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)
|
tool.setPartSize(partSize)
|
||||||
check, err = tool.start(SETTINGS.showProgress and not SETTINGS.multiThread)
|
check, err = tool.start(SETTINGS.showProgress and not SETTINGS.multiThread)
|
||||||
if not check:
|
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)
|
return False, str(err)
|
||||||
|
|
||||||
# encrypted -> decrypt and remove encrypted file
|
# 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)
|
__setMetaData__(track, album, path, contributors, lyrics)
|
||||||
Printf.success(track.title)
|
Printf.success(track.title)
|
||||||
|
|
||||||
return True, ''
|
return True, ''
|
||||||
except Exception as e:
|
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)
|
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):
|
def __getAlbum__(item: Track):
|
||||||
album = TIDAL_API.getAlbum(item.album.id)
|
album = TIDAL_API.getAlbum(item.album.id)
|
||||||
if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder:
|
if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder:
|
||||||
downloadCover(album)
|
downloadCover(album)
|
||||||
return album
|
return album
|
||||||
|
|
||||||
if not SETTINGS.multiThread:
|
if not SETTINGS.multiThread:
|
||||||
for index, item in enumerate(tracks):
|
for index, item in enumerate(tracks):
|
||||||
itemAlbum = album
|
itemAlbum = album
|
||||||
|
|||||||
@@ -6,17 +6,10 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 1.0
|
@Version : 1.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc :
|
@Desc :
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import aigpy
|
from download import *
|
||||||
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 *
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
=================================
|
=================================
|
||||||
|
|||||||
+137
-55
@@ -6,17 +6,13 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 1.0
|
@Version : 1.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc :
|
@Desc :
|
||||||
"""
|
"""
|
||||||
import sys
|
|
||||||
import aigpy
|
|
||||||
import _thread
|
|
||||||
import importlib
|
import importlib
|
||||||
|
import sys
|
||||||
|
|
||||||
from tidal_dl.events import *
|
from events import *
|
||||||
from tidal_dl.settings import *
|
from printf import *
|
||||||
from tidal_dl.printf import *
|
|
||||||
from tidal_dl.enums import *
|
|
||||||
|
|
||||||
|
|
||||||
def enableGui():
|
def enableGui():
|
||||||
@@ -33,41 +29,44 @@ if not enableGui():
|
|||||||
Printf.err("Not support gui. Please type: `pip3 install PyQt5 qt_material`")
|
Printf.err("Not support gui. Please type: `pip3 install PyQt5 qt_material`")
|
||||||
else:
|
else:
|
||||||
from PyQt5.QtCore import Qt, QObject
|
from PyQt5.QtCore import Qt, QObject
|
||||||
from PyQt5.QtGui import QTextCursor
|
from PyQt5.QtGui import QTextCursor, QKeyEvent
|
||||||
from PyQt5.QtCore import pyqtSignal
|
from PyQt5.QtCore import pyqtSignal
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
from qt_material import apply_stylesheet
|
from qt_material import apply_stylesheet
|
||||||
|
|
||||||
|
|
||||||
class SettingView(QtWidgets.QWidget):
|
class SettingView(QtWidgets.QWidget):
|
||||||
def __init__(self, ) -> None:
|
def __init__(self, ) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.initView()
|
self.initView()
|
||||||
|
|
||||||
def initView(self):
|
def initView(self):
|
||||||
self.c_pathDownload = QtWidgets.QLineEdit()
|
self.c_pathDownload = QtWidgets.QLineEdit()
|
||||||
self.c_pathAlbumFormat = QtWidgets.QLineEdit()
|
self.c_pathAlbumFormat = QtWidgets.QLineEdit()
|
||||||
self.c_pathTrackFormat = QtWidgets.QLineEdit()
|
self.c_pathTrackFormat = QtWidgets.QLineEdit()
|
||||||
self.c_pathVideoFormat = QtWidgets.QLineEdit()
|
self.c_pathVideoFormat = QtWidgets.QLineEdit()
|
||||||
|
|
||||||
self.mainGrid = QtWidgets.QVBoxLayout(self)
|
self.mainGrid = QtWidgets.QVBoxLayout(self)
|
||||||
self.mainGrid.addWidget(self.c_pathDownload)
|
self.mainGrid.addWidget(self.c_pathDownload)
|
||||||
self.mainGrid.addWidget(self.c_pathAlbumFormat)
|
self.mainGrid.addWidget(self.c_pathAlbumFormat)
|
||||||
self.mainGrid.addWidget(self.c_pathTrackFormat)
|
self.mainGrid.addWidget(self.c_pathTrackFormat)
|
||||||
self.mainGrid.addWidget(self.c_pathVideoFormat)
|
self.mainGrid.addWidget(self.c_pathVideoFormat)
|
||||||
|
|
||||||
|
|
||||||
class EmittingStream(QObject):
|
class EmittingStream(QObject):
|
||||||
textWritten = pyqtSignal(str)
|
textWritten = pyqtSignal(str)
|
||||||
|
|
||||||
def write(self, text):
|
def write(self, text):
|
||||||
self.textWritten.emit(str(text))
|
self.textWritten.emit(str(text))
|
||||||
|
|
||||||
|
|
||||||
class MainView(QtWidgets.QWidget):
|
class MainView(QtWidgets.QWidget):
|
||||||
s_downloadEnd = pyqtSignal(str, bool, str)
|
s_downloadEnd = pyqtSignal(str, bool, str)
|
||||||
|
|
||||||
def __init__(self, ) -> None:
|
def __init__(self, ) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.initView()
|
self.initView()
|
||||||
self.setMinimumSize(600, 620)
|
self.setMinimumSize(800, 620)
|
||||||
self.setWindowTitle("Tidal-dl")
|
self.setWindowTitle("Tidal-dl")
|
||||||
|
|
||||||
def __info__(self, msg):
|
def __info__(self, msg):
|
||||||
@@ -95,7 +94,7 @@ else:
|
|||||||
self.m_supportType = [Type.Album, Type.Playlist, Type.Track, Type.Video, Type.Artist]
|
self.m_supportType = [Type.Album, Type.Playlist, Type.Track, Type.Video, Type.Artist]
|
||||||
for item in self.m_supportType:
|
for item in self.m_supportType:
|
||||||
self.c_combType.addItem(item.name, item)
|
self.c_combType.addItem(item.name, item)
|
||||||
|
|
||||||
for item in AudioQuality:
|
for item in AudioQuality:
|
||||||
self.c_combTQuality.addItem(item.name, item)
|
self.c_combTQuality.addItem(item.name, item)
|
||||||
for item in VideoQuality:
|
for item in VideoQuality:
|
||||||
@@ -111,7 +110,7 @@ else:
|
|||||||
self.c_tableInfo.setShowGrid(False)
|
self.c_tableInfo.setShowGrid(False)
|
||||||
self.c_tableInfo.verticalHeader().setVisible(False)
|
self.c_tableInfo.verticalHeader().setVisible(False)
|
||||||
self.c_tableInfo.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
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().setStretchLastSection(True)
|
||||||
self.c_tableInfo.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
self.c_tableInfo.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
||||||
self.c_tableInfo.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
self.c_tableInfo.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
||||||
@@ -120,10 +119,20 @@ else:
|
|||||||
item = QtWidgets.QTableWidgetItem(name)
|
item = QtWidgets.QTableWidgetItem(name)
|
||||||
self.c_tableInfo.setHorizontalHeaderItem(index, item)
|
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
|
# print
|
||||||
self.c_printTextEdit = QtWidgets.QTextEdit()
|
self.c_printTextEdit = QtWidgets.QTextEdit()
|
||||||
self.c_printTextEdit.setReadOnly(True)
|
self.c_printTextEdit.setReadOnly(True)
|
||||||
self.c_printTextEdit.setFixedHeight(150)
|
self.c_printTextEdit.setFixedHeight(100)
|
||||||
sys.stdout = EmittingStream(textWritten=self.__output__)
|
sys.stdout = EmittingStream(textWritten=self.__output__)
|
||||||
sys.stderr = EmittingStream(textWritten=self.__output__)
|
sys.stderr = EmittingStream(textWritten=self.__output__)
|
||||||
|
|
||||||
@@ -146,18 +155,31 @@ else:
|
|||||||
self.funcGrid.addWidget(self.c_tableInfo)
|
self.funcGrid.addWidget(self.c_tableInfo)
|
||||||
self.funcGrid.addLayout(self.line2Grid)
|
self.funcGrid.addLayout(self.line2Grid)
|
||||||
self.funcGrid.addWidget(self.c_printTextEdit)
|
self.funcGrid.addWidget(self.c_printTextEdit)
|
||||||
|
|
||||||
self.mainGrid = QtWidgets.QGridLayout(self)
|
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)
|
self.mainGrid.addWidget(self.c_widgetSetting, 0, 0)
|
||||||
|
|
||||||
# connect
|
# connect
|
||||||
self.c_btnSearch.clicked.connect(self.search)
|
self.c_btnSearch.clicked.connect(self.search)
|
||||||
|
self.c_lineSearch.returnPressed.connect(self.search)
|
||||||
self.c_btnDownload.clicked.connect(self.download)
|
self.c_btnDownload.clicked.connect(self.download)
|
||||||
self.s_downloadEnd.connect(self.downloadEnd)
|
self.s_downloadEnd.connect(self.downloadEnd)
|
||||||
self.c_combTQuality.currentIndexChanged.connect(self.changeTQuality)
|
self.c_combTQuality.currentIndexChanged.connect(self.changeTQuality)
|
||||||
self.c_combVQuality.currentIndexChanged.connect(self.changeVQuality)
|
self.c_combVQuality.currentIndexChanged.connect(self.changeVQuality)
|
||||||
self.c_btnSetting.clicked.connect(self.showSettings)
|
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):
|
def addItem(self, rowIdx: int, colIdx: int, text):
|
||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
@@ -166,9 +188,9 @@ else:
|
|||||||
|
|
||||||
def search(self):
|
def search(self):
|
||||||
self.c_tableInfo.setRowCount(0)
|
self.c_tableInfo.setRowCount(0)
|
||||||
|
|
||||||
self.s_type = self.c_combType.currentData()
|
self.s_type = self.c_combType.currentData()
|
||||||
self.s_text = self.c_lineSearch.text()
|
self.s_text = self.c_lineSearch.text()
|
||||||
|
|
||||||
if self.s_text.startswith('http'):
|
if self.s_text.startswith('http'):
|
||||||
tmpType, tmpId = TIDAL_API.parseUrl(self.s_text)
|
tmpType, tmpId = TIDAL_API.parseUrl(self.s_text)
|
||||||
if tmpType == Type.Null:
|
if tmpType == Type.Null:
|
||||||
@@ -194,22 +216,27 @@ else:
|
|||||||
self.__info__('No result!')
|
self.__info__('No result!')
|
||||||
return
|
return
|
||||||
|
|
||||||
self.c_tableInfo.setRowCount(len(self.s_array))
|
self.set_table_search_results(self.s_array, self.s_type)
|
||||||
for index, item in enumerate(self.s_array):
|
|
||||||
|
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))
|
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, 1, item.title)
|
||||||
self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists))
|
self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists))
|
||||||
self.addItem(index, 3, item.audioQuality)
|
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, 1, item.title)
|
||||||
self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists))
|
self.addItem(index, 2, TIDAL_API.getArtistsName(item.artists))
|
||||||
self.addItem(index, 3, item.quality)
|
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, 1, item.title)
|
||||||
self.addItem(index, 2, '')
|
self.addItem(index, 2, '')
|
||||||
self.addItem(index, 3, '')
|
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, 1, item.name)
|
||||||
self.addItem(index, 2, '')
|
self.addItem(index, 2, '')
|
||||||
self.addItem(index, 3, '')
|
self.addItem(index, 3, '')
|
||||||
@@ -217,58 +244,113 @@ else:
|
|||||||
|
|
||||||
def download(self):
|
def download(self):
|
||||||
index = self.c_tableInfo.currentIndex().row()
|
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.')
|
self.__info__('Please select a row first.')
|
||||||
return
|
return
|
||||||
|
|
||||||
self.c_btnDownload.setEnabled(False)
|
rows = self.c_tableInfo.selectionModel().selectedRows()
|
||||||
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}]...")
|
|
||||||
|
|
||||||
def __thread_download__(model: MainView):
|
for row in rows:
|
||||||
downloading_item = ""
|
index = row.row()
|
||||||
try:
|
item = self.s_array[index]
|
||||||
type = model.s_type
|
item_type = self.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))
|
|
||||||
|
|
||||||
_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):
|
def downloadEnd(self, title, result, msg):
|
||||||
self.c_btnDownload.setEnabled(True)
|
self.c_btnDownload.setEnabled(True)
|
||||||
self.c_btnDownload.setText(f"Download")
|
self.c_btnDownload.setText(f"Download")
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
self.__info__(f'Download [{title}] finish')
|
Printf.info(f"Download '{title}' finished.")
|
||||||
else:
|
else:
|
||||||
self.__info__(f'Download [{title}] failed:{msg}')
|
Printf.err(f"Download '{title}' failed:{msg}")
|
||||||
|
|
||||||
def checkLogin(self):
|
def checkLogin(self):
|
||||||
if not loginByConfig():
|
if not loginByConfig():
|
||||||
self.__info__('Login failed. Please log in using the command line first.')
|
self.__info__('Login failed. Please log in using the command line first.')
|
||||||
|
|
||||||
def changeTQuality(self, index):
|
def changeTQuality(self, index):
|
||||||
SETTINGS.audioQuality = self.c_combTQuality.itemData(index)
|
SETTINGS.audioQuality = self.c_combTQuality.itemData(index)
|
||||||
SETTINGS.save()
|
SETTINGS.save()
|
||||||
|
|
||||||
def changeVQuality(self, index):
|
def changeVQuality(self, index):
|
||||||
SETTINGS.videoQuality = self.c_combVQuality.itemData(index)
|
SETTINGS.videoQuality = self.c_combVQuality.itemData(index)
|
||||||
SETTINGS.save()
|
SETTINGS.save()
|
||||||
|
|
||||||
def showSettings(self):
|
def showSettings(self):
|
||||||
self.c_widgetSetting.show()
|
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():
|
def startGui():
|
||||||
aigpy.cmd.enableColor(False)
|
aigpy.cmd.enableColor(False)
|
||||||
|
|
||||||
@@ -278,10 +360,10 @@ else:
|
|||||||
window = MainView()
|
window = MainView()
|
||||||
window.show()
|
window.show()
|
||||||
window.checkLogin()
|
window.checkLogin()
|
||||||
|
window.tree_items_playlists()
|
||||||
|
|
||||||
app.exec_()
|
app.exec_()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
SETTINGS.read(getProfilePath())
|
SETTINGS.read(getProfilePath())
|
||||||
TOKEN.read(getTokenPath())
|
TOKEN.read(getTokenPath())
|
||||||
|
|||||||
@@ -6,31 +6,31 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 1.0
|
@Version : 1.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc :
|
@Desc :
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from tidal_dl.lang.arabic import LangArabic
|
from lang.arabic import LangArabic
|
||||||
from tidal_dl.lang.chinese import LangChinese
|
from lang.chinese import LangChinese
|
||||||
from tidal_dl.lang.croatian import LangCroatian
|
from lang.croatian import LangCroatian
|
||||||
from tidal_dl.lang.czech import LangCzech
|
from lang.czech import LangCzech
|
||||||
from tidal_dl.lang.danish import LangDanish
|
from lang.danish import LangDanish
|
||||||
from tidal_dl.lang.dutch import LangDutch
|
from lang.dutch import LangDutch
|
||||||
from tidal_dl.lang.english import LangEnglish
|
from lang.english import LangEnglish
|
||||||
from tidal_dl.lang.filipino import LangFilipino
|
from lang.filipino import LangFilipino
|
||||||
from tidal_dl.lang.french import LangFrench
|
from lang.french import LangFrench
|
||||||
from tidal_dl.lang.german import LangGerman
|
from lang.german import LangGerman
|
||||||
from tidal_dl.lang.hungarian import LangHungarian
|
from lang.hungarian import LangHungarian
|
||||||
from tidal_dl.lang.italian import LangItalian
|
from lang.italian import LangItalian
|
||||||
from tidal_dl.lang.norwegian import LangNorwegian
|
from lang.norwegian import LangNorwegian
|
||||||
from tidal_dl.lang.polish import LangPolish
|
from lang.polish import LangPolish
|
||||||
from tidal_dl.lang.portuguese import LangPortuguese
|
from lang.portuguese import LangPortuguese
|
||||||
from tidal_dl.lang.russian import LangRussian
|
from lang.russian import LangRussian
|
||||||
from tidal_dl.lang.spanish import LangSpanish
|
from lang.spanish import LangSpanish
|
||||||
from tidal_dl.lang.turkish import LangTurkish
|
from lang.turkish import LangTurkish
|
||||||
from tidal_dl.lang.ukrainian import LangUkrainian
|
from lang.ukrainian import LangUkrainian
|
||||||
from tidal_dl.lang.vietnamese import LangVietnamese
|
from lang.vietnamese import LangVietnamese
|
||||||
from tidal_dl.lang.korean import LangKorean
|
from lang.korean import LangKorean
|
||||||
from tidal_dl.lang.japanese import LangJapanese
|
from lang.japanese import LangJapanese
|
||||||
|
|
||||||
_ALL_LANGUAGE_ = [
|
_ALL_LANGUAGE_ = [
|
||||||
['English', LangEnglish()],
|
['English', LangEnglish()],
|
||||||
@@ -66,7 +66,7 @@ class Language(object):
|
|||||||
return int(str)
|
return int(str)
|
||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def setLang(self, index):
|
def setLang(self, index):
|
||||||
index = self.__toInt__(index)
|
index = self.__toInt__(index)
|
||||||
if index >= 0 and index < len(_ALL_LANGUAGE_):
|
if index >= 0 and index < len(_ALL_LANGUAGE_):
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 1.0
|
@Version : 1.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc :
|
@Desc :
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import aigpy
|
import aigpy
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from tidal_dl.tidal import *
|
from tidal import *
|
||||||
from tidal_dl.settings import *
|
from settings import *
|
||||||
|
|
||||||
|
|
||||||
def __fixPath__(name: str):
|
def __fixPath__(name: str):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
@Author : Yaronzz
|
@Author : Yaronzz
|
||||||
@Version : 3.0
|
@Version : 3.0
|
||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc :
|
@Desc :
|
||||||
'''
|
'''
|
||||||
from pickle import GLOBAL
|
from pickle import GLOBAL
|
||||||
import threading
|
import threading
|
||||||
@@ -14,12 +14,12 @@ import aigpy
|
|||||||
import logging
|
import logging
|
||||||
import prettytable
|
import prettytable
|
||||||
|
|
||||||
import tidal_dl.apiKey as apiKey
|
import apiKey as apiKey
|
||||||
|
|
||||||
from tidal_dl.model import *
|
from model import *
|
||||||
from tidal_dl.paths import *
|
from paths import *
|
||||||
from tidal_dl.settings import *
|
from settings import *
|
||||||
from tidal_dl.lang.language import *
|
from lang.language import *
|
||||||
|
|
||||||
|
|
||||||
VERSION = '2022.10.31.1'
|
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}
|
{VERSION}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ class Printf(object):
|
|||||||
for item in rows:
|
for item in rows:
|
||||||
tb.add_row(item)
|
tb.add_row(item)
|
||||||
return tb
|
return tb
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def usage():
|
def usage():
|
||||||
print("=============TIDAL-DL HELP==============")
|
print("=============TIDAL-DL HELP==============")
|
||||||
@@ -70,7 +70,7 @@ class Printf(object):
|
|||||||
["-r or --resolution", "video resolution('P1080', 'P720', 'P480', 'P360')"]
|
["-r or --resolution", "video resolution('P1080', 'P720', 'P480', 'P360')"]
|
||||||
])
|
])
|
||||||
print(tb)
|
print(tb)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def checkVersion():
|
def checkVersion():
|
||||||
onlineVer = aigpy.pip.getLastVersion('tidal-dl')
|
onlineVer = aigpy.pip.getLastVersion('tidal-dl')
|
||||||
@@ -90,11 +90,11 @@ class Printf(object):
|
|||||||
[LANG.select.SETTING_PLAYLIST_FOLDER_FORMAT, data.playlistFolderFormat],
|
[LANG.select.SETTING_PLAYLIST_FOLDER_FORMAT, data.playlistFolderFormat],
|
||||||
[LANG.select.SETTING_TRACK_FILE_FORMAT, data.trackFileFormat],
|
[LANG.select.SETTING_TRACK_FILE_FORMAT, data.trackFileFormat],
|
||||||
[LANG.select.SETTING_VIDEO_FILE_FORMAT, data.videoFileFormat],
|
[LANG.select.SETTING_VIDEO_FILE_FORMAT, data.videoFileFormat],
|
||||||
|
|
||||||
#settings - quality
|
#settings - quality
|
||||||
[LANG.select.SETTING_AUDIO_QUALITY, data.audioQuality],
|
[LANG.select.SETTING_AUDIO_QUALITY, data.audioQuality],
|
||||||
[LANG.select.SETTING_VIDEO_QUALITY, data.videoQuality],
|
[LANG.select.SETTING_VIDEO_QUALITY, data.videoQuality],
|
||||||
|
|
||||||
#settings - else
|
#settings - else
|
||||||
[LANG.select.SETTING_USE_PLAYLIST_FOLDER, data.usePlaylistFolder],
|
[LANG.select.SETTING_USE_PLAYLIST_FOLDER, data.usePlaylistFolder],
|
||||||
[LANG.select.SETTING_CHECK_EXIST, data.checkExist],
|
[LANG.select.SETTING_CHECK_EXIST, data.checkExist],
|
||||||
@@ -135,7 +135,7 @@ class Printf(object):
|
|||||||
aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None)
|
aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None)
|
||||||
ret = input("")
|
ret = input("")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def enterBool(string):
|
def enterBool(string):
|
||||||
aigpy.cmd.colorPrint(string, aigpy.cmd.TextColor.Yellow, None)
|
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)
|
print(aigpy.cmd.red(LANG.select.PRINT_ERR + " ") + string)
|
||||||
# logging.error(string)
|
# logging.error(string)
|
||||||
print_mutex.release()
|
print_mutex.release()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def info(string):
|
def info(string):
|
||||||
global print_mutex
|
global print_mutex
|
||||||
@@ -295,15 +295,15 @@ class Printf(object):
|
|||||||
def apikeys(items):
|
def apikeys(items):
|
||||||
print("-------------API-KEYS---------------")
|
print("-------------API-KEYS---------------")
|
||||||
tb = prettytable.PrettyTable()
|
tb = prettytable.PrettyTable()
|
||||||
tb.field_names = [aigpy.cmd.green('Index'),
|
tb.field_names = [aigpy.cmd.green('Index'),
|
||||||
aigpy.cmd.green('Valid'),
|
aigpy.cmd.green('Valid'),
|
||||||
aigpy.cmd.green('Platform'),
|
aigpy.cmd.green('Platform'),
|
||||||
aigpy.cmd.green('Formats'), ]
|
aigpy.cmd.green('Formats'), ]
|
||||||
tb.align = 'l'
|
tb.align = 'l'
|
||||||
|
|
||||||
for index, item in enumerate(items):
|
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'),
|
aigpy.cmd.green('True') if item["valid"] == "True" else aigpy.cmd.red('False'),
|
||||||
item["platform"],
|
item["platform"],
|
||||||
item["formats"]])
|
item["formats"]])
|
||||||
print(tb)
|
print(tb)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ import json
|
|||||||
import aigpy
|
import aigpy
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from tidal_dl.lang.language import *
|
from lang.language import *
|
||||||
from tidal_dl.enums import *
|
from enums import *
|
||||||
|
|
||||||
|
|
||||||
class Settings(aigpy.model.ModelBase):
|
class Settings(aigpy.model.ModelBase):
|
||||||
@@ -61,7 +61,7 @@ class Settings(aigpy.model.ModelBase):
|
|||||||
if item.name == value:
|
if item.name == value:
|
||||||
return item
|
return item
|
||||||
return VideoQuality.P360
|
return VideoQuality.P360
|
||||||
|
|
||||||
def read(self, path):
|
def read(self, path):
|
||||||
self._path_ = path
|
self._path_ = path
|
||||||
txt = aigpy.file.getContent(self._path_)
|
txt = aigpy.file.getContent(self._path_)
|
||||||
@@ -83,7 +83,7 @@ class Settings(aigpy.model.ModelBase):
|
|||||||
self.videoFileFormat = self.getDefaultPathFormat(Type.Video)
|
self.videoFileFormat = self.getDefaultPathFormat(Type.Video)
|
||||||
if self.apiKeyIndex is None:
|
if self.apiKeyIndex is None:
|
||||||
self.apiKeyIndex = 0
|
self.apiKeyIndex = 0
|
||||||
|
|
||||||
LANG.setLang(self.language)
|
LANG.setLang(self.language)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
|||||||
@@ -8,18 +8,17 @@
|
|||||||
@Contact : yaronhuang@foxmail.com
|
@Contact : yaronhuang@foxmail.com
|
||||||
@Desc : tidal api
|
@Desc : tidal api
|
||||||
'''
|
'''
|
||||||
import json
|
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import aigpy
|
from typing import Union, List
|
||||||
import base64
|
|
||||||
import requests
|
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
from tidal_dl.model import *
|
import requests
|
||||||
from tidal_dl.enums import *
|
import tidalapi
|
||||||
from tidal_dl.settings import *
|
|
||||||
|
from model import *
|
||||||
|
from settings import *
|
||||||
|
|
||||||
# SSL Warnings | retry number
|
# SSL Warnings | retry number
|
||||||
requests.packages.urllib3.disable_warnings()
|
requests.packages.urllib3.disable_warnings()
|
||||||
@@ -43,7 +42,8 @@ class TidalAPI(object):
|
|||||||
if respond.url.find("playbackinfopostpaywall") != -1 and SETTINGS.downloadDelay is not False:
|
if respond.url.find("playbackinfopostpaywall") != -1 and SETTINGS.downloadDelay is not False:
|
||||||
# random sleep between 0.5 and 5 seconds and print it
|
# random sleep between 0.5 and 5 seconds and print it
|
||||||
sleep_time = random.randint(500, 5000) / 1000
|
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)
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
if respond.status_code == 429:
|
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'):
|
def __post__(self, path, data, auth=None, urlpre='https://auth.tidal.com/v1/oauth2'):
|
||||||
for index in range(3):
|
for index in range(3):
|
||||||
try:
|
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
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if index == 2:
|
if index == 2:
|
||||||
@@ -157,8 +157,14 @@ class TidalAPI(object):
|
|||||||
def verifyAccessToken(self, accessToken) -> bool:
|
def verifyAccessToken(self, accessToken) -> bool:
|
||||||
header = {'authorization': 'Bearer {}'.format(accessToken)}
|
header = {'authorization': 'Bearer {}'.format(accessToken)}
|
||||||
result = requests.get('https://api.tidal.com/v1/sessions', headers=header).json()
|
result = requests.get('https://api.tidal.com/v1/sessions', headers=header).json()
|
||||||
|
|
||||||
if 'status' in result and result['status'] != 200:
|
if 'status' in result and result['status'] != 200:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Set tidalapi session.
|
||||||
|
self.session = tidalapi.session.Session()
|
||||||
|
self.session.load_oauth_session("Bearer", accessToken)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def refreshAccessToken(self, refreshToken) -> bool:
|
def refreshAccessToken(self, refreshToken) -> bool:
|
||||||
@@ -188,11 +194,12 @@ class TidalAPI(object):
|
|||||||
|
|
||||||
if not aigpy.string.isNull(userid):
|
if not aigpy.string.isNull(userid):
|
||||||
if str(result['userId']) != str(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.userId = result['userId']
|
||||||
self.key.countryCode = result['countryCode']
|
self.key.countryCode = result['countryCode']
|
||||||
self.key.accessToken = accessToken
|
self.key.accessToken = accessToken
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def getAlbum(self, id) -> Album:
|
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:
|
def search(self, text: str, type: Type, offset: int = 0, limit: int = 10) -> SearchResult:
|
||||||
typeStr = type.name.upper() + "S"
|
typeStr = type.name.upper() + "S"
|
||||||
|
|
||||||
if type == Type.Null:
|
if type == Type.Null:
|
||||||
typeStr = "ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS"
|
typeStr = "ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS"
|
||||||
|
|
||||||
@@ -341,7 +349,7 @@ class TidalAPI(object):
|
|||||||
|
|
||||||
tracks.append(track_urls)
|
tracks.append(track_urls)
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
def getStreamUrl(self, id, quality: AudioQuality):
|
def getStreamUrl(self, id, quality: AudioQuality):
|
||||||
squality = "HI_RES"
|
squality = "HI_RES"
|
||||||
if quality == AudioQuality.Normal:
|
if quality == AudioQuality.Normal:
|
||||||
@@ -373,12 +381,12 @@ class TidalAPI(object):
|
|||||||
ret.trackid = resp.trackid
|
ret.trackid = resp.trackid
|
||||||
ret.soundQuality = resp.audioQuality
|
ret.soundQuality = resp.audioQuality
|
||||||
ret.codec = aigpy.string.getSub(xmldata, 'codecs="', '"')
|
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]
|
ret.urls = self.parse_mpd(xmldata)[0]
|
||||||
if len(ret.urls) > 0:
|
if len(ret.urls) > 0:
|
||||||
ret.url = ret.urls[0]
|
ret.url = ret.urls[0]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
raise Exception("Can't get the streamUrl, type is " + resp.manifestMimeType)
|
raise Exception("Can't get the streamUrl, type is " + resp.manifestMimeType)
|
||||||
|
|
||||||
def getVideoStreamUrl(self, id, quality: VideoQuality):
|
def getVideoStreamUrl(self, id, quality: VideoQuality):
|
||||||
@@ -474,6 +482,17 @@ class TidalAPI(object):
|
|||||||
|
|
||||||
raise Exception("No result.")
|
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
|
# Singleton
|
||||||
TIDAL_API = TidalAPI()
|
TIDAL_API = TidalAPI()
|
||||||
|
|||||||
Reference in New Issue
Block a user