mirror of
https://github.com/yaronzz/Tidal-Media-Downloader.git
synced 2026-06-13 04:05:07 +03:00
1. add simple-gui
This commit is contained in:
+1
-1
@@ -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.",
|
||||
|
||||
@@ -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
|
||||
@@ -52,6 +57,10 @@ def mainCommand():
|
||||
Printf.err(LANG.MSG_PATH_ERR + SETTINGS.downloadPath)
|
||||
return
|
||||
|
||||
if showGui:
|
||||
startGui()
|
||||
return
|
||||
|
||||
if link is not None:
|
||||
if not loginByConfig():
|
||||
loginByWeb()
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
'''
|
||||
=================================
|
||||
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
@@ -368,7 +381,6 @@ class TidalAPI(object):
|
||||
|
||||
raise Exception("No result.")
|
||||
|
||||
|
||||
# Singleton
|
||||
TIDAL_API = TidalAPI()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user