From d643de43e9e4d27fbe074667c74f97bb07389d6d Mon Sep 17 00:00:00 2001 From: Yaronzz Date: Thu, 23 Jun 2022 10:03:17 +0800 Subject: [PATCH] 1. add simple-gui --- TIDALDL-PY/setup.py | 2 +- TIDALDL-PY/tidal_dl/__init__.py | 14 ++- TIDALDL-PY/tidal_dl/download.py | 3 +- TIDALDL-PY/tidal_dl/events.py | 157 +++++++++++++--------------- TIDALDL-PY/tidal_dl/gui.py | 180 ++++++++++++++++++++++++++++++++ TIDALDL-PY/tidal_dl/tidal.py | 26 +++-- 6 files changed, 286 insertions(+), 96 deletions(-) create mode 100644 TIDALDL-PY/tidal_dl/gui.py diff --git a/TIDALDL-PY/setup.py b/TIDALDL-PY/setup.py index e38a29d..3d953ee 100644 --- a/TIDALDL-PY/setup.py +++ b/TIDALDL-PY/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages from tidal_dl.printf import VERSION setup( - name='tidal-dl', + name='tidal-dl-test', version=VERSION, license="Apache2", description="Tidal Music Downloader.", diff --git a/TIDALDL-PY/tidal_dl/__init__.py b/TIDALDL-PY/tidal_dl/__init__.py index 7c59342..78e9b8a 100644 --- a/TIDALDL-PY/tidal_dl/__init__.py +++ b/TIDALDL-PY/tidal_dl/__init__.py @@ -13,18 +13,20 @@ import getopt from tidal_dl.events import * from tidal_dl.settings import * +from tidal_dl.gui import * def mainCommand(): try: opts, args = getopt.getopt(sys.argv[1:], - "hvl:o:q:r:", - ["help", "version", "link=", "output=", "quality", "resolution"]) + "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.") return link = None + showGui = False for opt, val in opts: if opt in ('-h', '--help'): Printf.usage() @@ -32,6 +34,9 @@ def mainCommand(): if opt in ('-v', '--version'): Printf.logo() return + if opt in ('-g', '--gui'): + showGui = True + return if opt in ('-l', '--link'): link = val continue @@ -51,6 +56,10 @@ def mainCommand(): if not aigpy.path.mkdirs(SETTINGS.downloadPath): Printf.err(LANG.MSG_PATH_ERR + SETTINGS.downloadPath) return + + if showGui: + startGui() + return if link is not None: if not loginByConfig(): @@ -123,6 +132,7 @@ def test(): Printf.settings() # test example + # https://tidal.com/browse/track/70973230 # track 70973230 77798028 212657 start('70973230') # album 58138532 77803199 21993753 79151897 56288918 diff --git a/TIDALDL-PY/tidal_dl/download.py b/TIDALDL-PY/tidal_dl/download.py index ab8b1a3..54bcc41 100644 --- a/TIDALDL-PY/tidal_dl/download.py +++ b/TIDALDL-PY/tidal_dl/download.py @@ -14,6 +14,7 @@ import logging from tidal_dl.paths import * from tidal_dl.printf import * from tidal_dl.decryption import * +from tidal_dl.tidal import * def __isSkip__(finalpath, url): @@ -91,7 +92,7 @@ def downloadAlbumInfo(album, tracks): infos = "" infos += "[ID] %s\n" % (str(album.id)) infos += "[Title] %s\n" % (str(album.title)) - infos += "[Artists] %s\n" % (str(album.artist.name)) + infos += "[Artists] %s\n" % (TIDAL_API.getArtistsName(album.artists)) infos += "[ReleaseDate] %s\n" % (str(album.releaseDate)) infos += "[SongNum] %s\n" % (str(album.numberOfTracks)) infos += "[Duration] %s\n" % (str(album.duration)) diff --git a/TIDALDL-PY/tidal_dl/events.py b/TIDALDL-PY/tidal_dl/events.py index 929342d..28d6fd6 100644 --- a/TIDALDL-PY/tidal_dl/events.py +++ b/TIDALDL-PY/tidal_dl/events.py @@ -25,84 +25,66 @@ START DOWNLOAD ''' -def __album__(obj: Album): - try: - Printf.album(obj) - tracks, videos = TIDAL_API.getItems(obj.id, Type.Album) - if SETTINGS.saveAlbumInfo: - downloadAlbumInfo(obj, tracks) - if SETTINGS.saveCovers: - downloadCover(obj) - for item in tracks: - downloadTrack(item, obj) - for item in videos: - downloadVideo(item, obj) - except Exception as e: - Printf.err(str(e)) +def start_album(obj: Album): + Printf.album(obj) + tracks, videos = TIDAL_API.getItems(obj.id, Type.Album) + if SETTINGS.saveAlbumInfo: + downloadAlbumInfo(obj, tracks) + if SETTINGS.saveCovers: + downloadCover(obj) + for item in tracks: + downloadTrack(item, obj) + for item in videos: + downloadVideo(item, obj) -def __track__(obj: Track): - try: - album = TIDAL_API.getAlbum(obj.album.id) - if SETTINGS.saveCovers: +def start_track(obj: Track): + album = TIDAL_API.getAlbum(obj.album.id) + if SETTINGS.saveCovers: + downloadCover(album) + downloadTrack(obj, album) + + +def start_video(obj: Video): + # Printf.video(obj) + downloadVideo(obj, obj.album) + + +def start_artist(obj: Artist): + albums = TIDAL_API.getArtistAlbums(obj.id, SETTINGS.includeEP) + Printf.artist(obj, len(albums)) + for item in albums: + start_album(item) + + +def start_playlist(obj: Playlist): + Printf.playlist(obj) + tracks, videos = TIDAL_API.getItems(obj.uuid, Type.Playlist) + + for index, item in enumerate(tracks): + album = TIDAL_API.getAlbum(item.album.id) + item.trackNumberOnPlaylist = index + 1 + downloadTrack(item, album, obj) + if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder: downloadCover(album) - downloadTrack(obj, album) - except Exception as e: - Printf.err(str(e)) + for item in videos: + downloadVideo(item, None) -def __video__(obj: Video): - try: - # Printf.video(obj) - downloadVideo(obj, obj.album) - except Exception as e: - Printf.err(str(e)) +def start_mix(obj: Mix): + Printf.mix(obj) + for index, item in enumerate(obj.tracks): + album = TIDAL_API.getAlbum(item.album.id) + item.trackNumberOnPlaylist = index + 1 + downloadTrack(item, album) + if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder: + downloadCover(album) + + for item in obj.videos: + downloadVideo(item, None) -def __artist__(obj: Artist): - try: - albums = TIDAL_API.getArtistAlbums(obj.id, SETTINGS.includeEP) - Printf.artist(obj, len(albums)) - for item in albums: - __album__(item) - except Exception as e: - Printf.err(str(e)) - - -def __playlist__(obj: Playlist): - try: - Printf.playlist(obj) - tracks, videos = TIDAL_API.getItems(obj.uuid, Type.Playlist) - - for index, item in enumerate(tracks): - album = TIDAL_API.getAlbum(item.album.id) - item.trackNumberOnPlaylist = index + 1 - downloadTrack(item, album, obj) - if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder: - downloadCover(album) - for item in videos: - downloadVideo(item, None) - except Exception as e: - Printf.err(str(e)) - - -def __mix__(obj: Mix): - try: - Printf.mix(obj) - for index, item in enumerate(obj.tracks): - album = TIDAL_API.getAlbum(item.album.id) - item.trackNumberOnPlaylist = index + 1 - downloadTrack(item, album) - if SETTINGS.saveCovers and not SETTINGS.usePlaylistFolder: - downloadCover(album) - - for item in obj.videos: - downloadVideo(item, None) - except Exception as e: - Printf.err(str(e)) - - -def __dealFile__(string): +def start_file(string): txt = aigpy.file.getContent(string) if aigpy.string.isNull(txt): Printf.err("Nothing can read!") @@ -118,6 +100,20 @@ def __dealFile__(string): start(item) +def start_type(etype: Type, obj): + if etype == Type.Album: + start_album(obj) + elif etype == Type.Track: + start_track(obj) + elif etype == Type.Video: + start_video(obj) + elif etype == Type.Artist: + start_artist(obj) + elif etype == Type.Playlist: + start_playlist(obj) + elif etype == Type.Mix: + start_mix(obj) + def start(string): if aigpy.string.isNull(string): Printf.err('Please enter something.') @@ -128,7 +124,7 @@ def start(string): if aigpy.string.isNull(item): continue if os.path.exists(item): - __dealFile__(item) + start_file(item) return try: @@ -137,19 +133,10 @@ def start(string): Printf.err(str(e) + " [" + item + "]") return - if etype == Type.Album: - __album__(obj) - elif etype == Type.Track: - __track__(obj) - elif etype == Type.Video: - __video__(obj) - elif etype == Type.Artist: - __artist__(obj) - elif etype == Type.Playlist: - __playlist__(obj) - elif etype == Type.Mix: - __mix__(obj) - + try: + start_type(etype, obj) + except Exception as e: + Printf.err(str(e)) ''' ================================= diff --git a/TIDALDL-PY/tidal_dl/gui.py b/TIDALDL-PY/tidal_dl/gui.py new file mode 100644 index 0000000..0e9e5f4 --- /dev/null +++ b/TIDALDL-PY/tidal_dl/gui.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +""" +@File : test.py +@Date : 2022/03/28 +@Author : Yaronzz +@Version : 1.0 +@Contact : yaronhuang@foxmail.com +@Desc : +""" +import os +import sys +import _thread + +from tidal_dl.events import * +from tidal_dl.settings import * + +from PyQt5.QtCore import Qt +from PyQt5.QtCore import pyqtSignal +from PyQt5 import QtWidgets +from qt_material import apply_stylesheet + + +class MainView(QtWidgets.QWidget): + s_downloadEnd = pyqtSignal(str, bool, str) + + def __init__(self, ) -> None: + super().__init__() + self.initView() + self.setMinimumSize(600, 500) + self.setWindowTitle("Tidal-dl") + + def __info__(self, msg): + QtWidgets.QMessageBox.information(self, + 'Info', + msg, + QtWidgets.QMessageBox.Yes) + + def initView(self): + self.c_lineSearch = QtWidgets.QLineEdit() + self.c_btnSearch = QtWidgets.QPushButton("Search") + self.c_btnDownload = QtWidgets.QPushButton("Download") + + self.m_supportType = [Type.Album, Type.Playlist, Type.Track, Type.Video] + self.c_combType = QtWidgets.QComboBox() + for item in self.m_supportType: + self.c_combType.addItem(item.name, item) + + columnNames = ['#', 'Title', 'Artists', 'Quality'] + self.c_tableInfo = QtWidgets.QTableWidget() + self.c_tableInfo.setColumnCount(len(columnNames)) + self.c_tableInfo.setRowCount(0) + 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.horizontalHeader().setStretchLastSection(True) + self.c_tableInfo.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.ResizeMode.ResizeToContents) + self.c_tableInfo.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.c_tableInfo.setFocusPolicy(Qt.NoFocus) + for index, name in enumerate(columnNames): + item = QtWidgets.QTableWidgetItem(name) + self.c_tableInfo.setHorizontalHeaderItem(index, item) + + self.lineGrid = QtWidgets.QHBoxLayout() + self.lineGrid.addWidget(self.c_combType) + self.lineGrid.addWidget(self.c_lineSearch) + self.lineGrid.addWidget(self.c_btnSearch) + + self.mainGrid = QtWidgets.QVBoxLayout(self) + self.mainGrid.addLayout(self.lineGrid) + self.mainGrid.addWidget(self.c_tableInfo) + self.mainGrid.addWidget(self.c_btnDownload) + + self.c_btnSearch.clicked.connect(self.search) + self.c_btnDownload.clicked.connect(self.download) + self.s_downloadEnd.connect(self.downloadEnd) + + def addItem(self, rowIdx: int, colIdx: int, text): + if isinstance(text, str): + item = QtWidgets.QTableWidgetItem(text) + self.c_tableInfo.setItem(rowIdx, colIdx, item) + + 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: + self.__info__('Url not support!') + return + elif tmpType not in self.m_supportType: + self.__info__(f'Type[{tmpType.name}] not support!') + return + + tmpData = TIDAL_API.getTypeData(tmpId, tmpType) + if tmpData is None: + self.__info__('Url is wrong!') + return + self.s_type = tmpType + self.s_array = [tmpData] + self.s_result = None + self.c_combType.setCurrentText(tmpType.name) + else: + self.s_result = TIDAL_API.search(self.s_text, self.s_type) + self.s_array = TIDAL_API.getSearchResultItems(self.s_result, self.s_type) + + if len(self.s_array) <= 0: + self.__info__('No result!') + return + + self.c_tableInfo.setRowCount(len(self.s_array)) + for index, item in enumerate(self.s_array): + self.addItem(index, 0, str(index + 1)) + if self.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]: + 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]: + self.addItem(index, 1, item.title) + self.addItem(index, 2, '') + self.addItem(index, 3, '') + + def download(self): + index = self.c_tableInfo.currentIndex().row() + if index < 0: + self.__info__('Please select a row first.') + return + + self.c_btnDownload.setEnabled(False) + self.c_btnDownload.setText(f"Downloading [{self.s_array[index].title}]...") + + def __thread_download__(model: MainView): + try: + type = model.s_type + item = model.s_array[index] + start_type(type, item) + model.s_downloadEnd.emit(item.title, True, '') + except Exception as e: + model.s_downloadEnd.emit(item.title, False, str(e)) + + _thread.start_new_thread(__thread_download__, (self, )) + + 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') + else: + 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.') + + +def startGui(): + os.chdir(sys.path[0]) + + app = QtWidgets.QApplication(sys.argv) + apply_stylesheet(app, theme='dark_blue.xml') + + window = MainView() + window.show() + window.checkLogin() + + app.exec_() + + +if __name__ == '__main__': + SETTINGS.read(getProfilePath()) + TOKEN.read(getTokenPath()) + startGui() diff --git a/TIDALDL-PY/tidal_dl/tidal.py b/TIDALDL-PY/tidal_dl/tidal.py index 680674e..c4ba944 100644 --- a/TIDALDL-PY/tidal_dl/tidal.py +++ b/TIDALDL-PY/tidal_dl/tidal.py @@ -45,7 +45,7 @@ class TidalAPI(object): except Exception as e: if index >= 3: errmsg += respond.text - + raise Exception(errmsg) def __getItems__(self, path, params={}): @@ -69,7 +69,7 @@ class TidalAPI(object): def __getResolutionList__(self, url): ret = [] - array = requests.get(url).text.split("#") + array = requests.get(url).content.decode('utf-8').split("#") for item in array: if "RESOLUTION=" not in item: continue @@ -209,8 +209,8 @@ class TidalAPI(object): return self.getMix(id) return None - def search(self, text: str, type: Type, offset: int, limit: int) -> SearchResult: - typeStr = Type.Album.name.upper() + "S" + 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" @@ -220,6 +220,19 @@ class TidalAPI(object): "types": typeStr} return aigpy.model.dictToModel(self.__get__('search', params=params), SearchResult()) + def getSearchResultItems(self, result: SearchResult, type: Type): + if type == Type.Track: + return result.tracks.items + if type == Type.Video: + return result.videos.items + if type == Type.Album: + return result.albums.items + if type == Type.Artist: + return result.artists.items + if type == Type.Playlist: + return result.playlists.items + return [] + def getLyrics(self, id) -> Lyrics: data = self.__get__(f'tracks/{str(id)}/lyrics', urlpre='https://listen.tidal.com/v1/') return aigpy.model.dictToModel(data, Lyrics()) @@ -365,10 +378,9 @@ class TidalAPI(object): return item, obj except: continue - + raise Exception("No result.") + # Singleton TIDAL_API = TidalAPI() - -