diff --git a/gui/gui.py b/gui/gui.py index dcea363..6e9ace5 100644 --- a/gui/gui.py +++ b/gui/gui.py @@ -176,7 +176,7 @@ class Ui_MainWindow(object): self.edit_second_pads_collection_lay.addWidget(self.edit_second_pads_collection_label) self.edit_second_pads_collection_list = QtWidgets.QListWidget(self.edit_second_pads_collection_widget) self.edit_second_pads_collection_list.setStyleSheet("font: 15pt \"Segoe UI\";") - self.edit_second_pads_collection_list.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed|QtWidgets.QAbstractItemView.SelectedClicked) + self.edit_second_pads_collection_list.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers) self.edit_second_pads_collection_list.setDragEnabled(True) self.edit_second_pads_collection_list.setObjectName("edit_second_pads_collection_list") self.edit_second_pads_collection_lay.addWidget(self.edit_second_pads_collection_list) @@ -327,6 +327,39 @@ class Ui_MainWindow(object): self.content.addWidget(self.collab_page) self.download_page = QtWidgets.QWidget() self.download_page.setObjectName("download_page") + self.download_page_lay = QtWidgets.QVBoxLayout(self.download_page) + self.download_page_lay.setContentsMargins(0, 0, 0, 0) + self.download_page_lay.setObjectName("download_page_lay") + self.download_url_box = QtWidgets.QLineEdit(self.download_page) + self.download_url_box.setMinimumSize(QtCore.QSize(0, 35)) + self.download_url_box.setObjectName("download_url_box") + self.download_page_lay.addWidget(self.download_url_box) + self.download_path_widget = QtWidgets.QWidget(self.download_page) + self.download_path_widget.setObjectName("download_path_widget") + self.download_path_widget_lay = QtWidgets.QHBoxLayout(self.download_path_widget) + self.download_path_widget_lay.setContentsMargins(0, 0, 0, 0) + self.download_path_widget_lay.setObjectName("download_path_widget_lay") + self.download_to_path_box = QtWidgets.QLineEdit(self.download_path_widget) + self.download_to_path_box.setMinimumSize(QtCore.QSize(0, 35)) + self.download_to_path_box.setObjectName("download_to_path_box") + self.download_path_widget_lay.addWidget(self.download_to_path_box) + self.choose_download_path_button = QtWidgets.QPushButton(self.download_path_widget) + self.choose_download_path_button.setMinimumSize(QtCore.QSize(100, 35)) + self.choose_download_path_button.setObjectName("choose_download_path_button") + self.download_path_widget_lay.addWidget(self.choose_download_path_button) + self.download_page_lay.addWidget(self.download_path_widget) + self.download_track_button = QtWidgets.QPushButton(self.download_page) + self.download_track_button.setMinimumSize(QtCore.QSize(0, 35)) + self.download_track_button.setObjectName("download_track_button") + self.download_page_lay.addWidget(self.download_track_button) + self.download_track_logs = QtWidgets.QTextBrowser(self.download_page) + self.download_track_logs.setObjectName("download_track_logs") + self.download_page_lay.addWidget(self.download_track_logs) + self.download_track_progress = QtWidgets.QProgressBar(self.download_page) + self.download_track_progress.setProperty("value", 0) + self.download_track_progress.setTextVisible(False) + self.download_track_progress.setObjectName("download_track_progress") + self.download_page_lay.addWidget(self.download_track_progress) self.content.addWidget(self.download_page) self.settings_page = QtWidgets.QWidget() self.settings_page.setObjectName("settings_page") @@ -578,6 +611,10 @@ class Ui_MainWindow(object): self.collections_page_tabs.setTabText(self.collections_page_tabs.indexOf(self.edit_collections_tabs), _translate("MainWindow", "Edit collections")) self.to_stream_url_box.setPlaceholderText(_translate("MainWindow", "URL (direct web file, YouTube or spotify link) or path to file")) self.play_stream_button.setText(_translate("MainWindow", "Play")) + self.download_url_box.setPlaceholderText(_translate("MainWindow", "URL (direct web file, YouTube or spotify link) or path to file")) + self.download_to_path_box.setPlaceholderText(_translate("MainWindow", "File download folder")) + self.choose_download_path_button.setText(_translate("MainWindow", "Choose path")) + self.download_track_button.setText(_translate("MainWindow", "Download")) self.play_options_group.setTitle(_translate("MainWindow", "Play options")) self.output_device_play_label.setText(_translate("MainWindow", "Output device (or virtual mic input)")) self.preview_device_play_label.setText(_translate("MainWindow", "Preview device (your headphones)")) diff --git a/gui/gui.ui b/gui/gui.ui index 8fe6106..e7d874f 100644 --- a/gui/gui.ui +++ b/gui/gui.ui @@ -301,6 +301,20 @@ QSlider::handle:horizontal:hover { QSlider::handle:horizontal:pressed { background-color: #bfbfbf; } + +QProgressBar { + text-align: center; + color: white; + border-width: 1px; + border-radius: 10px; + border-color: #3a3a3a; + border-style: inset; + background-color:#202020; +} +QProgressBar::chunk { + background-color: #848484; + border-radius: 5px; +} @@ -690,7 +704,7 @@ QListWidget:item:selected { font: 15pt "Segoe UI"; - QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + QAbstractItemView::AllEditTriggers true @@ -1059,7 +1073,105 @@ QListWidget:item:selected { - + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 35 + + + + URL (direct web file, YouTube or spotify link) or path to file + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 35 + + + + File download folder + + + + + + + + 100 + 35 + + + + Choose path + + + + + + + + + + + 0 + 35 + + + + Download + + + + + + + + + + 0 + + + false + + + + + diff --git a/gui/modules/download/__init__.py b/gui/modules/download/__init__.py new file mode 100644 index 0000000..d7ed779 --- /dev/null +++ b/gui/modules/download/__init__.py @@ -0,0 +1 @@ +from .handlers import * diff --git a/gui/modules/download/downloader.py b/gui/modules/download/downloader.py new file mode 100644 index 0000000..a819769 --- /dev/null +++ b/gui/modules/download/downloader.py @@ -0,0 +1,42 @@ +import time + +import validators +from ezzthread import threaded +from gui.modules.core.qt_updater import call +from gui.gui import Ui_MainWindow +from modules.player import convert +from modules.spotify.spotify_dl import Spotify +from urllib.request import urlopen +from functools import partial +import os + + +@threaded +def download_track(ui: Ui_MainWindow): + url = ui.download_url_box.text() + + if not validators.url(url): + call(ui.download_track_logs.append, f"{url} is not valid URL, skipping") + return + + call(ui.download_track_logs.append, f"Downloading {url}") + + name = (lambda song: song.artist + " - " + song.name + ".mp3")(Spotify().get_song(url)) \ + if "spotify" in url or "youtu" in url else url.split('/')[-1] + + url = convert.get_raw_link(url) + + call(ui.download_track_button.setEnabled, False) + + response = urlopen(url) + call(ui.download_track_progress.setValue, 0) + size = int(response.info()["Content-length"]) + downloaded = 0 + with open(os.path.join(ui.download_to_path_box.text(), name), "wb") as dest_file: + for data in iter(partial(response.read, 4096), b""): + downloaded += len(data) + dest_file.write(data) + call(ui.download_track_progress.setValue, int(downloaded / size * 100)) + + call(ui.download_track_button.setEnabled, True) + call(ui.download_track_logs.append, f"Downloaded to {os.path.join(ui.download_to_path_box.text(), name)}") diff --git a/gui/modules/download/handlers.py b/gui/modules/download/handlers.py new file mode 100644 index 0000000..7882a42 --- /dev/null +++ b/gui/modules/download/handlers.py @@ -0,0 +1,13 @@ +from gui.gui import Ui_MainWindow +from PyQt5.QtWidgets import QFileDialog +from gui.modules.download import downloader + + +def register_handlers(ui: Ui_MainWindow): + ui.choose_download_path_button.clicked.connect( + lambda: ui.download_to_path_box.setText( + QFileDialog.getExistingDirectory(caption="Select where to download file") + ) + ) + + ui.download_track_button.clicked.connect(lambda: downloader.download_track(ui)) diff --git a/gui/modules/handlers/register.py b/gui/modules/handlers/register.py index 497c652..39bf4a9 100644 --- a/gui/modules/handlers/register.py +++ b/gui/modules/handlers/register.py @@ -8,6 +8,7 @@ from gui.modules import restreammic from gui.modules import explorer from gui.modules import collections from gui.modules import stream +from gui.modules import download from modules.player.player import Player from modules.restream.restream import Restreamer @@ -21,3 +22,4 @@ def register_handlers(ui: Ui_MainWindow, MainWindow: QMainWindow, p: Player, rs: explorer.register_handlers(ui, p) collections.register_handlers(ui, p) stream.register_handlers(ui, p) + download.register_handlers(ui) diff --git a/modules/spotify/spotify_dl.py b/modules/spotify/spotify_dl.py index 23e14f0..9aff44b 100644 --- a/modules/spotify/spotify_dl.py +++ b/modules/spotify/spotify_dl.py @@ -5,12 +5,19 @@ from urllib.error import HTTPError import re from base64 import b64encode from modules.spotify.config import SpotifyConfig +import pafy def reencode(text: str): return b64encode(text.encode()).decode() +class Song(object): + def __init__(self, artist: str, name: str): + self.artist = artist + self.name = name + + class Spotify(object): def __init__(self): try: @@ -29,25 +36,34 @@ class Spotify(object): except Exception as e: print(e) + def get_song(self, url) -> Song | None: + if "spotify" in url: + track_id = url.split('/')[-1].split('?')[0] + r = requests.get(f"https://api.spotify.com/v1/tracks/{track_id}", + headers={'Authorization': f'Bearer {self.token}'}) + + if r.status_code == 400 or r.status_code == 401: + return None + + track = r.json() + + song_name = track['name'] + artists = [] + + for artist in track['artists']: + artists.append(artist['name']) + artist_name = ' '.join(artists) + + return Song(artist_name, song_name) + else: + video = pafy.new(url) + return Song(video.author, video.title) + def get_youtube_url(self, url) -> str | None: - track_id = url.split('/')[-1].split('?')[0] - r = requests.get(f"https://api.spotify.com/v1/tracks/{track_id}", - headers={'Authorization': f'Bearer {self.token}'}) - - if r.status_code == 400 or r.status_code == 401: - return None - - track = r.json() - - song_name = track['name'] - artists = [] - - for artist in track['artists']: - artists.append(artist['name']) - artist_name = ' '.join(artists) + song = self.get_song(url) try: - query_string = urlencode({'search_query': artist_name + ' ' + song_name}) + query_string = urlencode({'search_query': song.artist + ' ' + song.name}) htm_content = urlopen('http://www.youtube.com/results?' + query_string) search_results = re.findall(r'/watch\?v=(.{11})', htm_content.read().decode())