Compare commits

...

11 Commits

Author SHA1 Message Date
Yaronzz f4854f4b74 Fix formatting issues in README.md
Build exe / build (macos-latest) (push) Has been cancelled
Build exe / build (ubuntu-latest) (push) Has been cancelled
Build exe / build (windows-latest) (push) Has been cancelled
2025-10-17 11:41:40 +08:00
Yaronzz 8352f0f6e2 UPDATE 2023-11-01 13:41:16 +08:00
Yaronzz a8da5c0ff3 Merge pull request #1106 from exislow/gui-playlists
[GUI] Show user's playlists and allow to download them.
2023-10-31 13:34:49 +08:00
Robert Honz df7c444487 Added playlist -> right click to download the whole playlist in "playlist style". 2023-10-31 06:08:40 +01:00
Robert Honz d88dca4da6 Style fix. 2023-10-23 21:55:57 +02:00
Robert Honz 88b5b61328 KeyPressEvent select all items. 2023-10-23 21:55:26 +02:00
Robert Honz 56c9cf5695 Refactoring: Mainly removed unused imports. 2023-10-23 15:00:06 +02:00
Robert Honz 379eca8458 Multiple title can be downloaded sequentially. 2023-10-23 14:34:14 +02:00
Robert Honz 7d6f9fc6e1 * Playlist content will be displayed in table.
* Can select multiple items to download.
2023-10-23 13:41:17 +02:00
Robert Honz 87eabab36f * Added tidalapi as dependency.
* Added TreeView to display user's playlists.
2023-10-22 21:16:20 +02:00
Robert Honz 288c75bcc7 Removed tidal. import. 2023-10-22 10:51:47 +02:00
12 changed files with 203 additions and 187 deletions
+4 -4
View File
@@ -14,7 +14,7 @@
"console": "integratedTerminal",
// "python": "python3",
"env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/"
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
},
"justMyCode": false
},
@@ -26,7 +26,7 @@
"console": "integratedTerminal",
// "python": "python3",
"env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/"
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
},
"args": [
"--link",
@@ -46,7 +46,7 @@
"console": "integratedTerminal",
// "python": "python3",
"env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/"
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
},
"justMyCode": false
@@ -59,7 +59,7 @@
"console": "integratedTerminal",
// "python": "python3",
"env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/"
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
},
"justMyCode": false
-1
View File
@@ -154,4 +154,3 @@ pip3 uninstall tidal-dl
pip3 install -r requirements.txt --user
python3 setup.py install
```
+1 -1
View File
@@ -9,4 +9,4 @@ lyricsgenius==3.0.1
pydub==0.25.1
PyQt5==5.15.7
qt-material==2.12
lxml==4.7.1
+22 -17
View File
@@ -6,27 +6,30 @@
@Author : Yaronzz
@Version : 3.0
@Contact : yaronhuang@foxmail.com
@Desc :
@Desc :
'''
import sys
import getopt
import aigpy
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
from printf import Printf
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 +55,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 +74,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 +118,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
@@ -140,6 +143,8 @@ def test():
SETTINGS.checkExist = False
Printf.settings()
TIDAL_API.getPlaylistSelf()
# test example
# https://tidal.com/browse/track/70973230
# track 70973230 77798028 212657
@@ -149,7 +154,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__':
+12 -13
View File
@@ -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
+2 -9
View File
@@ -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 *
'''
=================================
+89 -77
View File
@@ -6,17 +6,14 @@
@Author : Yaronzz
@Version : 1.0
@Contact : yaronhuang@foxmail.com
@Desc :
@Desc :
"""
import sys
import aigpy
import _thread
import importlib
import sys
import _thread
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,42 +30,25 @@ 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.setWindowTitle("Tidal-dl")
self.setMinimumSize(800, 620)
self.setWindowTitle("TIDAL-DL")
def __info__(self, msg):
QtWidgets.QMessageBox.information(self, 'Info', msg, QtWidgets.QMessageBox.Yes)
@@ -84,18 +64,15 @@ else:
self.c_lineSearch = QtWidgets.QLineEdit()
self.c_btnSearch = QtWidgets.QPushButton("Search")
self.c_btnDownload = QtWidgets.QPushButton("Download")
self.c_btnSetting = QtWidgets.QPushButton("Setting")
self.c_combType = QtWidgets.QComboBox()
self.c_combTQuality = QtWidgets.QComboBox()
self.c_combVQuality = QtWidgets.QComboBox()
self.c_widgetSetting = SettingView()
self.c_widgetSetting.hide()
# Supported types for search
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 +88,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 +97,21 @@ 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.setFixedWidth(200)
self.tree_playlists.setColumnCount(1)
self.tree_playlists.setHeaderLabels(("User Playlists",))
# self.tree_playlists.setColumnWidth(0, 100)
self.tree_playlists.setContextMenuPolicy(Qt.CustomContextMenu)
# 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__)
@@ -138,7 +126,6 @@ else:
self.line2Grid.addWidget(self.c_combTQuality)
self.line2Grid.addWidget(self.c_combVQuality)
self.line2Grid.addStretch(4)
# self.line2Grid.addWidget(self.c_btnSetting)
self.line2Grid.addWidget(self.c_btnDownload)
self.funcGrid = QtWidgets.QVBoxLayout()
@@ -146,18 +133,23 @@ 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.c_widgetSetting, 0, 0)
self.mainGrid = QtWidgets.QHBoxLayout(self)
self.mainGrid.addWidget(self.tree_playlists)
self.mainGrid.addLayout(self.funcGrid)
# 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.__displayTracks__)
def keyPressEvent(self, event: QKeyEvent):
if event.modifiers() & Qt.MetaModifier and event.key() == Qt.Key_A:
self.c_tableInfo.selectAll()
def addItem(self, rowIdx: int, colIdx: int, text):
if isinstance(text, str):
@@ -166,9 +158,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,81 +186,102 @@ else:
self.__info__('No result')
return
self.c_tableInfo.setRowCount(len(self.s_array))
for index, item in enumerate(self.s_array):
self.setSearchResults(self.s_array, self.s_type)
def setSearchResults(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, '')
self.c_tableInfo.viewport().update()
def download(self):
index = self.c_tableInfo.currentIndex().row()
if index < 0:
if self.c_tableInfo.selectionModel().hasSelection() == False:
self.__info__('Please select a row first.')
return
rows = self.c_tableInfo.selectionModel().selectedRows()
items = []
for row in rows:
items.append(self.s_array[row.row()])
self.__downloadFunc__(items)
def __downloadFunc__(self, items):
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}]...")
def __thread_download__(model: MainView):
downloading_item = ""
def __thread_download__(model: MainView, items):
itemTitle = ''
type = model.s_type
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, '')
for item in items:
if isinstance(item, Artist):
itemTitle = item.name
else:
itemTitle = item.title
start_type(type, item)
model.s_downloadEnd.emit('Download Success!', True, '')
except Exception as e:
model.s_downloadEnd.emit(downloading_item, False, str(e))
model.s_downloadEnd.emit(itemTitle, False, str(e))
_thread.start_new_thread(__thread_download__, (self, ))
_thread.start_new_thread(__thread_download__, (self, items))
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')
self.__info__(f"Download finished.")
else:
self.__info__(f'Download [{title}] failed:{msg}')
self.__info__(f"Download '{title}' failed:{msg}")
def checkLogin(self):
if not loginByConfig():
self.__info__('Login failed. Please log in using the command line first.')
else:
self.__showSelfPlaylists__()
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 __showSelfPlaylists__(self):
playlists = TIDAL_API.getPlaylistSelf()
for playlist in playlists:
item = QtWidgets.QTreeWidgetItem(self.tree_playlists)
item.setText(0, playlist.title)
item.setText(1, str(playlist.numberOfTracks))
item.setText(2, playlist.uuid)
def __displayTracks__(self, item, column):
tracks, videos = TIDAL_API.getItems(item.text(2), Type.Playlist)
self.s_array = tracks
self.s_type = Type.Track
self.setSearchResults(tracks, Type.Track)
def startGui():
aigpy.cmd.enableColor(False)
@@ -281,7 +294,6 @@ else:
app.exec_()
if __name__ == '__main__':
SETTINGS.read(getProfilePath())
TOKEN.read(getTokenPath())
+24 -24
View File
@@ -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_):
+3 -3
View File
@@ -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):
+20 -20
View File
@@ -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)
+4 -4
View File
@@ -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):
+22 -14
View File
@@ -8,18 +8,16 @@
@Contact : yaronhuang@foxmail.com
@Desc : tidal api
'''
import json
import random
import re
import time
import aigpy
import base64
import requests
from typing import List
from xml.etree import ElementTree
from tidal_dl.model import *
from tidal_dl.enums import *
from tidal_dl.settings import *
import requests
from model import *
from settings import *
# SSL Warnings | retry number
requests.packages.urllib3.disable_warnings()
@@ -43,7 +41,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 +108,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,6 +156,7 @@ 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
return True
@@ -188,11 +188,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:
@@ -200,6 +201,13 @@ class TidalAPI(object):
def getPlaylist(self, id) -> Playlist:
return aigpy.model.dictToModel(self.__get__('playlists/' + str(id)), Playlist())
def getPlaylistSelf(self) -> List[Playlist]:
ret = self.__get__(f'users/{self.key.userId}/playlists')
playlists = []
for item in ret['items']:
playlists.append(aigpy.model.dictToModel(item, Playlist()))
return playlists
def getArtist(self, id) -> Artist:
return aigpy.model.dictToModel(self.__get__('artists/' + str(id)), Artist())
@@ -233,6 +241,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 +350,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 +382,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 +483,5 @@ class TidalAPI(object):
raise Exception("No result.")
# Singleton
TIDAL_API = TidalAPI()