This commit is contained in:
Yaronzz
2023-11-01 13:41:16 +08:00
parent a8da5c0ff3
commit 8352f0f6e2
5 changed files with 65 additions and 143 deletions

8
.vscode/launch.json vendored
View File

@@ -14,7 +14,7 @@
"console": "integratedTerminal", "console": "integratedTerminal",
// "python": "python3", // "python": "python3",
"env": { "env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/" "PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
}, },
"justMyCode": false "justMyCode": false
}, },
@@ -26,7 +26,7 @@
"console": "integratedTerminal", "console": "integratedTerminal",
// "python": "python3", // "python": "python3",
"env": { "env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/" "PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
}, },
"args": [ "args": [
"--link", "--link",
@@ -46,7 +46,7 @@
"console": "integratedTerminal", "console": "integratedTerminal",
// "python": "python3", // "python": "python3",
"env": { "env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/" "PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
}, },
"justMyCode": false "justMyCode": false
@@ -59,7 +59,7 @@
"console": "integratedTerminal", "console": "integratedTerminal",
// "python": "python3", // "python": "python3",
"env": { "env": {
"PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/" "PYTHONPATH": "${workspaceRoot}/TIDALDL-PY/tidal_dl/"
}, },
"justMyCode": false "justMyCode": false

View File

@@ -9,5 +9,4 @@ 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
tidalapi==0.7.3

View File

@@ -10,10 +10,12 @@
''' '''
import sys import sys
import getopt import getopt
import aigpy
from events import * from events import *
from settings import * from settings import *
from gui import startGui from gui import startGui
from printf import Printf
def mainCommand(): def mainCommand():
@@ -141,6 +143,8 @@ def test():
SETTINGS.checkExist = False SETTINGS.checkExist = False
Printf.settings() Printf.settings()
TIDAL_API.getPlaylistSelf()
# test example # test example
# https://tidal.com/browse/track/70973230 # https://tidal.com/browse/track/70973230
# track 70973230 77798028 212657 # track 70973230 77798028 212657

View File

@@ -10,6 +10,7 @@
""" """
import importlib import importlib
import sys import sys
import _thread
from events import * from events import *
from printf import * from printf import *
@@ -34,32 +35,12 @@ else:
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from qt_material import apply_stylesheet 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): 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)
@@ -67,7 +48,7 @@ else:
super().__init__() super().__init__()
self.initView() self.initView()
self.setMinimumSize(800, 620) self.setMinimumSize(800, 620)
self.setWindowTitle("Tidal-dl") self.setWindowTitle("TIDAL-DL")
def __info__(self, msg): def __info__(self, msg):
QtWidgets.QMessageBox.information(self, 'Info', msg, QtWidgets.QMessageBox.Yes) QtWidgets.QMessageBox.information(self, 'Info', msg, QtWidgets.QMessageBox.Yes)
@@ -83,12 +64,9 @@ else:
self.c_lineSearch = QtWidgets.QLineEdit() self.c_lineSearch = QtWidgets.QLineEdit()
self.c_btnSearch = QtWidgets.QPushButton("Search") self.c_btnSearch = QtWidgets.QPushButton("Search")
self.c_btnDownload = QtWidgets.QPushButton("Download") self.c_btnDownload = QtWidgets.QPushButton("Download")
self.c_btnSetting = QtWidgets.QPushButton("Setting")
self.c_combType = QtWidgets.QComboBox() self.c_combType = QtWidgets.QComboBox()
self.c_combTQuality = QtWidgets.QComboBox() self.c_combTQuality = QtWidgets.QComboBox()
self.c_combVQuality = QtWidgets.QComboBox() self.c_combVQuality = QtWidgets.QComboBox()
self.c_widgetSetting = SettingView()
self.c_widgetSetting.hide()
# Supported types for search # Supported types for search
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]
@@ -124,10 +102,11 @@ else:
self.tree_playlists.setAnimated(False) self.tree_playlists.setAnimated(False)
self.tree_playlists.setIndentation(20) self.tree_playlists.setIndentation(20)
self.tree_playlists.setSortingEnabled(True) self.tree_playlists.setSortingEnabled(True)
self.tree_playlists.resize(200, 400) self.tree_playlists.setFixedWidth(200)
self.tree_playlists.setColumnCount(2) self.tree_playlists.setColumnCount(1)
self.tree_playlists.setHeaderLabels(("Name", "# Tracks")) self.tree_playlists.setHeaderLabels(("User Playlists",))
self.tree_playlists.setColumnWidth(0, 200) # self.tree_playlists.setColumnWidth(0, 100)
self.tree_playlists.setContextMenuPolicy(Qt.CustomContextMenu)
# print # print
self.c_printTextEdit = QtWidgets.QTextEdit() self.c_printTextEdit = QtWidgets.QTextEdit()
@@ -147,7 +126,6 @@ else:
self.line2Grid.addWidget(self.c_combTQuality) self.line2Grid.addWidget(self.c_combTQuality)
self.line2Grid.addWidget(self.c_combVQuality) self.line2Grid.addWidget(self.c_combVQuality)
self.line2Grid.addStretch(4) self.line2Grid.addStretch(4)
# self.line2Grid.addWidget(self.c_btnSetting)
self.line2Grid.addWidget(self.c_btnDownload) self.line2Grid.addWidget(self.c_btnDownload)
self.funcGrid = QtWidgets.QVBoxLayout() self.funcGrid = QtWidgets.QVBoxLayout()
@@ -156,10 +134,9 @@ else:
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.QHBoxLayout(self)
self.mainGrid.addWidget(self.tree_playlists, 0, 0, 1, 2) self.mainGrid.addWidget(self.tree_playlists)
self.mainGrid.addLayout(self.funcGrid, 0, 2, 1, 3) self.mainGrid.addLayout(self.funcGrid)
self.mainGrid.addWidget(self.c_widgetSetting, 0, 0)
# connect # connect
self.c_btnSearch.clicked.connect(self.search) self.c_btnSearch.clicked.connect(self.search)
@@ -168,17 +145,10 @@ else:
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.tree_playlists.itemClicked.connect(self.__displayTracks__)
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): def keyPressEvent(self, event: QKeyEvent):
key = event.key() if event.modifiers() & Qt.MetaModifier and event.key() == Qt.Key_A:
if event.modifiers() & Qt.MetaModifier and key == Qt.Key_A:
self.c_tableInfo.selectAll() self.c_tableInfo.selectAll()
def addItem(self, rowIdx: int, colIdx: int, text): def addItem(self, rowIdx: int, colIdx: int, text):
@@ -216,9 +186,9 @@ else:
self.__info__('No result') self.__info__('No result')
return return
self.set_table_search_results(self.s_array, self.s_type) self.setSearchResults(self.s_array, self.s_type)
def set_table_search_results(self, s_array, s_type): def setSearchResults(self, s_array, s_type):
self.c_tableInfo.clearSelection() self.c_tableInfo.clearSelection()
self.c_tableInfo.setRowCount(len(s_array)) self.c_tableInfo.setRowCount(len(s_array))
@@ -243,63 +213,50 @@ else:
self.c_tableInfo.viewport().update() self.c_tableInfo.viewport().update()
def download(self): def download(self):
index = self.c_tableInfo.currentIndex().row() if self.c_tableInfo.selectionModel().hasSelection() == False:
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
rows = self.c_tableInfo.selectionModel().selectedRows() rows = self.c_tableInfo.selectionModel().selectedRows()
items = []
for row in rows: for row in rows:
index = row.row() items.append(self.s_array[row.row()])
item = self.s_array[index]
item_type = self.s_type self.__downloadFunc__(items)
def __downloadFunc__(self, items):
self.c_btnDownload.setEnabled(False)
self.download_item(item, item_type) def __thread_download__(model: MainView, items):
itemTitle = ''
type = model.s_type
try:
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(itemTitle, False, str(e))
def download_item(self, item, item_type): _thread.start_new_thread(__thread_download__, (self, items))
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:
Printf.info(f"Download '{title}' finished.") self.__info__(f"Download finished.")
else: else:
Printf.err(f"Download '{title}' failed:{msg}") self.__info__(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.')
else:
self.__showSelfPlaylists__()
def changeTQuality(self, index): def changeTQuality(self, index):
SETTINGS.audioQuality = self.c_combTQuality.itemData(index) SETTINGS.audioQuality = self.c_combTQuality.itemData(index)
@@ -309,47 +266,21 @@ else:
SETTINGS.videoQuality = self.c_combVQuality.itemData(index) SETTINGS.videoQuality = self.c_combVQuality.itemData(index)
SETTINGS.save() SETTINGS.save()
def showSettings(self): def __showSelfPlaylists__(self):
self.c_widgetSetting.show() playlists = TIDAL_API.getPlaylistSelf()
def tree_items_playlists(self):
playlists = TIDAL_API.get_playlists()
for playlist in playlists: for playlist in playlists:
item = QtWidgets.QTreeWidgetItem(self.tree_playlists) item = QtWidgets.QTreeWidgetItem(self.tree_playlists)
item.setText(0, playlist.name) item.setText(0, playlist.title)
item.setText(1, str(playlist.num_tracks)) item.setText(1, str(playlist.numberOfTracks))
item.setText(2, playlist.id) item.setText(2, playlist.uuid)
def playlist_display_tracks(self, item, column): def __displayTracks__(self, item, column):
tracks = TIDAL_API.get_playlist_items(item.text(2)) tracks, videos = TIDAL_API.getItems(item.text(2), Type.Playlist)
self.s_array = tracks self.s_array = tracks
self.s_type = Type.Track self.s_type = Type.Track
self.set_table_search_results(tracks, Type.Track) self.setSearchResults(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)
@@ -360,7 +291,6 @@ else:
window = MainView() window = MainView()
window.show() window.show()
window.checkLogin() window.checkLogin()
window.tree_items_playlists()
app.exec_() app.exec_()

View File

@@ -11,11 +11,10 @@
import random import random
import re import re
import time import time
from typing import Union, List from typing import List
from xml.etree import ElementTree from xml.etree import ElementTree
import requests import requests
import tidalapi
from model import * from model import *
from settings import * from settings import *
@@ -160,11 +159,6 @@ class TidalAPI(object):
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:
@@ -207,6 +201,13 @@ class TidalAPI(object):
def getPlaylist(self, id) -> Playlist: def getPlaylist(self, id) -> Playlist:
return aigpy.model.dictToModel(self.__get__('playlists/' + str(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: def getArtist(self, id) -> Artist:
return aigpy.model.dictToModel(self.__get__('artists/' + str(id)), Artist()) return aigpy.model.dictToModel(self.__get__('artists/' + str(id)), Artist())
@@ -482,17 +483,5 @@ 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()