diff --git a/youtility/cut.py b/youtility/cut.py
index 6d7b81f..282019c 100644
--- a/youtility/cut.py
+++ b/youtility/cut.py
@@ -1,12 +1,15 @@
+import logging
import sys
-from PyQt6.QtCore import Qt, QThread, pyqtSignal, QObject
+from PyQt6.QtCore import Qt, QThread, pyqtSignal, QObject, pyqtSlot
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, \
QSpacerItem, QLabel, QFileDialog
from pytube import YouTube
from qfluentwidgets import (LineEdit,
ListWidget, PushButton, MessageBox, ProgressBar, TextEdit)
+logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
+
class Stream(QObject):
new_text = pyqtSignal(str)
@@ -20,7 +23,6 @@ def flush(self):
class DownloaderThread(QThread):
download_finished = pyqtSignal()
- on_progress = pyqtSignal(int)
new_text = pyqtSignal(str)
def __init__(self, link, start_time, end_time, save_path):
@@ -45,7 +47,6 @@ def run(self):
'verbose': True,
'download_ranges': download_range_func(None, [(start_time, end_time)]),
'force_keyframes_at_cuts': True,
- 'progress_hooks': [self.progress_hook],
}
# Redirect stdout and stderr
@@ -75,14 +76,6 @@ def show_message_box(self, title, message):
w.yesButton.setText('OK')
w.exec()
- def progress_hook(self, d):
- if d['status'] == 'downloading':
- total_bytes = d.get('total_bytes') or d.get('total_bytes_estimate')
- downloaded_bytes = d.get('downloaded_bytes')
- if total_bytes and downloaded_bytes:
- progress = int(downloaded_bytes / total_bytes * 100)
- self.on_progress.emit(progress)
-
def hhmmss_to_seconds(self, hhmmss):
h, m, s = map(int, hhmmss.split(':'))
return h * 3600 + m * 60 + s
@@ -130,9 +123,6 @@ def __init__(self):
self.main_layout.addSpacerItem(spacer_item_medium)
- self.progress_bar = ProgressBar()
- self.main_layout.addWidget(self.progress_bar)
-
self.main_layout.addSpacerItem(spacer_item_medium)
# Console Output
@@ -151,12 +141,17 @@ def __init__(self):
self.download_button.clicked.connect(self.download)
self.button_layout.addWidget(self.download_button)
- # GIF Loading Screen
- self.gif_layout = QHBoxLayout()
- self.main_layout.addLayout(self.gif_layout)
self.loading_label = QLabel()
self.main_layout.addWidget(self.loading_label)
+ self.main_layout.addSpacerItem(spacer_item_medium)
+ self.main_layout.addSpacerItem(spacer_item_medium)
+ self.main_layout.addSpacerItem(spacer_item_medium)
+ self.main_layout.addSpacerItem(spacer_item_medium)
+
+ disclaimer = QLabel("*** This feature uses YT-DLP and requires ffmpeg and will take slightly longer time to render, and the quality is also NOT adjustable.")
+ self.main_layout.addWidget(disclaimer)
+
self.count_layout = QHBoxLayout()
self.download_list_widget = ListWidget()
self.count_layout.addWidget(self.download_list_widget)
@@ -175,14 +170,12 @@ def download(self):
if save_path:
thread = DownloaderThread(link, start_time, end_time, save_path)
thread.download_finished.connect(self.show_download_finished_message)
- thread.on_progress.connect(self.update_progress_bar)
thread.new_text.connect(self.append_text)
thread.start()
- def update_progress_bar(self, value):
- self.progress_bar.setValue(value)
-
+ @pyqtSlot(str)
def append_text(self, text):
+ logging.debug(f"Appending text: {text}")
self.console_output.append(text)
def init_timers(self):
@@ -197,8 +190,8 @@ def init_timers(self):
length = (video.length)
length = self.seconds_to_hhmmss(length)
self.end_time.setText(length)
- except:
- pass
+ except Exception as e:
+ logging.error(f"Failed to initialize timers: {e}", exc_info=True)
def hhmmss_to_seconds(self, hhmmss):
h, m, s = map(int, hhmmss.split(':'))
diff --git a/youtility/downloader.py b/youtility/downloader.py
index d4c5f73..cb7c9bd 100644
--- a/youtility/downloader.py
+++ b/youtility/downloader.py
@@ -46,12 +46,6 @@ def __init__(self, link, quality, download_captions, copy_thumbnail_link, dwnld_
self.filename = filename
def run(self):
- def get_gif():
- gifs = ["loading.gif", "loading_2.gif"]
- gif = random.choice(gifs)
- gif_path = "resources/misc/" + gif
- return gif_path
-
caption_file_path = os.path.join(self.save_path, "captions.xml")
self.loading_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
@@ -161,10 +155,13 @@ def __init__(self):
self.quality_menu.setPlaceholderText("Video Quality (Enter link to view)")
# self.quality_menu.addItems(["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p"])
self.quality_layout.addWidget(self.quality_menu)
+
self.options_layout.addSpacerItem(spacer_item_medium)
self.thumbnail_url_checkbox = CheckBox('Copy Thumbnail Link', self)
+
self.audio_only_checkbox = CheckBox('Download Audio Only', self)
self.audio_only_checkbox.stateChanged.connect(self.update_audio_format)
+
self.captions_checkbox = CheckBox('Download Captions', self)
self.captions_checkbox.stateChanged.connect(self.trigger_captions_list)
diff --git a/youtility/playlist.py b/youtility/playlist.py
index 1becdd5..883f7a9 100644
--- a/youtility/playlist.py
+++ b/youtility/playlist.py
@@ -1,6 +1,8 @@
import json
+import os
import random
import re
+import subprocess
import pytube.exceptions
from PyQt6.QtCore import Qt, QThread, pyqtSignal
@@ -9,7 +11,7 @@
QSpacerItem, QLabel, QListWidgetItem
from pytube import Playlist
from qfluentwidgets import (LineEdit,
- CheckBox, ListWidget, TextEdit, PushButton)
+ CheckBox, ListWidget, TextEdit, PushButton, ComboBox)
with open("resources/misc/config.json", "r") as themes_file:
_themes = json.load(themes_file)
@@ -21,7 +23,7 @@ class DownloaderThread(QThread):
download_finished = pyqtSignal()
def __init__(self, link, quality, dwnld_list_widget, quality_menu,
- loading_label, main_window, save_path, progress_text, mp3_only, folder_path=None,
+ loading_label, main_window, save_path, progress_text, mp3_only, filename, audio_format, folder_path=None,
copy_thumbnail_link=None):
super().__init__()
self.link = link
@@ -35,6 +37,8 @@ def __init__(self, link, quality, dwnld_list_widget, quality_menu,
self.progress_textbox = progress_text
self.main_window = main_window
self.mp3_only = mp3_only
+ self.audio_format = audio_format
+ self.filename = filename
def run(self):
def get_gif():
@@ -73,14 +77,22 @@ def get_gif():
self.progress_textbox.append(
'Downloading: {} with URL: {}'.format((video.title + " -audio"), video.watch_url))
self.progress_textbox.append("\n")
-
filtered_streams = video.streams.filter(only_audio=True).first()
+ filtered_streams.download(output_path=self.save_path, filename=(video.title + ".mp3"))
+ self.progress_textbox.append('Downloaded: {}'.format(video.title))
- # selected_stream = filtered_streams.filter(only_audio=True).first()
-
- filtered_streams.download(output_path=self.save_path)
+ if self.audio_format == "FLAC":
+ input_file = os.path.join(self.save_path, (video.title + ".mp3")).replace("\\", "/")
+ output_file = os.path.join(self.save_path, (video.title + ".flac")).replace("\\", "/")
- self.progress_textbox.append('Downloaded: {}'.format(video.title))
+ # Run the ffmpeg command to convert mp4 to flac
+ ffmpeg_command = f'ffmpeg -i "{input_file}" "{output_file}"'
+ try:
+ subprocess.run(ffmpeg_command, shell=True, check=True)
+ os.remove(input_file)
+ except subprocess.CalledProcessError as e:
+ print(f"Error during conversion: {e}")
+ self.list_item.setText((title + " - Download failed during conversion"))
self.download_finished.emit()
self.list_item.setText((title + " - Downloaded"))
@@ -94,6 +106,7 @@ def __init__(self):
spacer_item_medium = QSpacerItem(0, 20)
self.setObjectName("Playlist")
+ self.audio_format_choice = ComboBox()
self.main_layout = QVBoxLayout()
self.main_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
@@ -114,7 +127,7 @@ def __init__(self):
self.options_layout = QHBoxLayout()
self.main_layout.addLayout(self.quality_layout)
self.main_layout.addLayout(self.options_layout)
- self.quality_menu = QComboBox()
+ self.quality_menu = ComboBox()
self.quality_menu.setPlaceholderText("Video Quality (Applies to all videos)")
if progressive == "True":
self.quality_menu.addItems(["720p", "480p", "360p", "240p", "144p"])
@@ -124,11 +137,13 @@ def __init__(self):
self.options_layout.addSpacerItem(spacer_item_medium)
self.thumbnail_url_checkbox = CheckBox('Copy Thumbnail URL', self)
self.audio_only_checkbox = CheckBox('Download Audio Only', self)
+ self.audio_only_checkbox.stateChanged.connect(self.audio_format_init)
self.options_group = QGroupBox("Additional Options")
self.options_group_layout = QVBoxLayout(self.options_group)
self.options_group_layout.addWidget(self.thumbnail_url_checkbox)
self.options_group_layout.addWidget(self.audio_only_checkbox)
+ self.options_group_layout.addSpacerItem(spacer_item_medium)
self.options_layout.addWidget(self.options_group)
self.main_layout.addSpacerItem(spacer_item_small)
@@ -146,15 +161,10 @@ def __init__(self):
self.download_button.clicked.connect(self.download)
self.button_layout.addWidget(self.download_button)
- # GIF Loading Screen
- self.gif_layout = QHBoxLayout()
- self.main_layout.addLayout(self.gif_layout)
self.loading_label = QLabel()
self.main_layout.addWidget(self.loading_label)
- # Progress Area
self.count_layout = QHBoxLayout()
- # Create a QListWidget to display downloading status
self.download_list_widget = ListWidget()
self.download_list_text = TextEdit()
self.download_list_text.setReadOnly(True)
@@ -165,6 +175,16 @@ def __init__(self):
self.setLayout(self.main_layout)
self.caption_list = None
+ def audio_format_init(self):
+ if self.audio_only_checkbox.isChecked():
+ audio_formats = ["MP3", "FLAC"]
+ self.audio_format_choice = ComboBox()
+ self.audio_format_choice.setCurrentText(_themes["def-audio-format"])
+ self.audio_format_choice.addItems(audio_formats)
+ self.options_group_layout.addWidget(self.audio_format_choice)
+ else:
+ self.audio_format_choice.hide()
+
def get_quality(self):
url = self.link_entry.text()
set_progressive = True
@@ -198,8 +218,10 @@ def download(self):
except pytube.exceptions.RegexMatchError:
title = "Untitled"
- # Open file dialog to get save path
+ audio_format = self.audio_format_choice.currentText()
save_path, _ = QFileDialog.getSaveFileName(self, "Save file", title)
+ filename = os.path.basename(save_path)
+ filename_without_extension, _ = os.path.splitext(filename)
self.downloader_thread = DownloaderThread(
link=link,
@@ -210,7 +232,9 @@ def download(self):
quality_menu=self.quality_menu,
main_window=self,
progress_text=self.download_list_text,
- mp3_only=mp3_only
+ mp3_only=mp3_only,
+ audio_format=audio_format,
+ filename=filename_without_extension
)
self.downloader_thread.download_finished.connect(self.show_download_finished_message)
self.downloader_thread.start()
diff --git a/youtility/settings.py b/youtility/settings.py
index 2168374..150ba01 100644
--- a/youtility/settings.py
+++ b/youtility/settings.py
@@ -1,7 +1,7 @@
import json
-from PyQt6.QtWidgets import QWidget, QVBoxLayout, QGroupBox, QPushButton, QComboBox
-from qfluentwidgets import (LineEdit, StrongBodyLabel, MessageBox, CheckBox)
+from PyQt6.QtWidgets import QWidget, QVBoxLayout, QGroupBox, QPushButton, QComboBox, QSpacerItem
+from qfluentwidgets import (LineEdit, StrongBodyLabel, MessageBox, CheckBox, ComboBox)
with open("resources/misc/config.json", "r") as themes_file:
_themes = json.load(themes_file)
@@ -14,6 +14,10 @@ def __init__(self):
self.initUI()
def initUI(self):
+
+ spacer_item_small = QSpacerItem(0, 10)
+ spacer_item_medium = QSpacerItem(0, 20)
+
layout = QVBoxLayout()
layout.addStretch()
@@ -36,15 +40,15 @@ def initUI(self):
def_sub_format_label = StrongBodyLabel("Default Subtitle Format: ", self)
pref_layout.addWidget(def_sub_format_label)
- self.def_sub_format = QComboBox()
+ self.def_sub_format = ComboBox()
self.def_sub_format.addItems(["SRT", "XML"])
self.def_sub_format.setCurrentText(_themes["def_sub_format"])
pref_layout.addWidget(self.def_sub_format)
- set_progressive_label = StrongBodyLabel("Allow higher res downloads (audio may be missing): ", self)
- pref_layout.addWidget(set_progressive_label)
+ pref_layout.addSpacerItem(spacer_item_medium)
+
self.set_progressive = CheckBox()
- self.set_progressive.setText("Allow")
+ self.set_progressive.setText("Allow higher res downloads (audio may be missing): ")
if _themes["progressive"] == "False":
self.set_progressive.setChecked(True)
else: