pyrip/pyripgui.py
2025-08-04 08:37:01 +02:00

211 lines
6.9 KiB
Python

import argparse
import fstools
from gui import MainWindow, SelectionDialog
import media
import os
from PySide6.QtCore import QObject, Signal, Slot
from PySide6.QtWidgets import QApplication, QMessageBox
import pyudev
from pyudev.pyside6 import MonitorObserver
from queue import Queue
import report
import sys
logger = report.app_logging_config()
# TODO: Stop / Kill Threads before close: QThread: Destroyed while thread '' is still running
# TODO: Stop Queues, on Error. Possibly better error handling.
# TODO: Error handling on cancelling cddb choose Dialog
MEDIA_GUI_DICT = {
MainWindow.KEY_ALBUM_ARTIST: media.KEY_ARTIST,
MainWindow.KEY_ALBUM_ALBUM: media.KEY_ALBUM,
MainWindow.KEY_ALBUM_YEAR: media.KEY_YEAR,
MainWindow.KEY_ALBUM_GENRE: media.KEY_GENRE
}
class RipQueueWorker(QObject):
progress_updated = Signal(int, int)
job_finished = Signal(int)
all_jobs_finished = Signal()
def __init__(self, basepath):
self.__basepath__ = basepath
self.__job_data__ = {}
super().__init__()
self.queue = Queue()
self.__error_msg__ = None
def get_error(self):
return self.__error_msg__
def set_error(self, error_msg):
if self.__error_msg__ is None:
self.__error_msg__ = error_msg
@Slot(int)
def add_job(self, job_index, job_data):
self.__job_data__[job_index] = job_data
self.queue.put(job_index)
def progress_adaption(self, value, track_num):
self.progress_updated.emit(50 * value, track_num - 1)
@Slot()
def run(self):
while True:
job_index = self.queue.get()
track_info = self.__job_data__.get(job_index)
if job_index is None or track_info is None:
break
# Rip track job_index
wavfile = media.track_to_targetpath(self.__basepath__, track_info, 'wav')
try:
fstools.mkdir(os.path.dirname(wavfile))
except PermissionError:
msg = f"Unable to create ripping target path: {os.path.dirname(wavfile)}"
logger.exception(msg)
self.set_error(msg)
break
if media.disc_track_rip(job_index + 1, wavfile, self.progress_adaption) != 0:
msg = f"Unable to rip: {wavfile}"
logger.exception(msg)
self.set_error(msg)
break
if job_index in self.__job_data__:
del self.__job_data__[job_index]
self.job_finished.emit(job_index)
self.all_jobs_finished.emit()
class EncodeQueueWorker(QObject):
progress_updated = Signal(int, int)
job_finished = Signal(int)
all_jobs_finished = Signal()
def __init__(self, basepath):
self.__basepath__ = basepath
self.__job_data__ = {}
super().__init__()
self.queue = Queue()
self.__error_msg__ = None
def get_error(self):
return self.__error_msg__
def set_error(self, error_msg):
if self.__error_msg__ is None:
self.__error_msg__ = error_msg
@Slot(int)
def add_job(self, job_index, job_data):
self.__job_data__[job_index] = job_data
self.queue.put(job_index)
def progress_adaption(self, value, track_num):
self.progress_updated.emit(50 + 50 * value, track_num - 1)
@Slot()
def run(self):
while True:
job_index = self.queue.get()
track_info = self.__job_data__.get(job_index)
if job_index is None or track_info is None:
break
# Rip track job_index
wavfile = media.track_to_targetpath(self.__basepath__, track_info, 'wav')
rv = media.wav_to_mp3(wavfile, self.__basepath__, track_info, self.progress_adaption)
os.remove(wavfile)
if rv != 0:
msg = f"Unable to encode: {wavfile}"
logger.exception(msg)
self.set_error(msg)
break
if job_index in self.__job_data__:
del self.__job_data__[job_index]
self.job_finished.emit(job_index)
self.all_jobs_finished.emit()
class RipMainWindow(MainWindow):
def read_it(self):
self.set_status("Reading data from disc...")
disc_data = media.get_media_data(media.get_disc_device(), self.cddb_choose_dialog)
# disc_data = media.metadata.get_disc_data_dummy()
if disc_data is None:
msg = "Could not read disc data!"
logger.error(msg)
self.set_status(msg)
QMessageBox.critical(self, "Error!", msg)
else:
self.clear()
# Set album info to gui
for key in MEDIA_GUI_DICT:
self.set_album_field(key, str(disc_data[MEDIA_GUI_DICT[key]]))
# Set tracks to gui
for track in disc_data[media.KEY_TRACKLIST]:
self.append_title(track[media.KEY_TITLE])
# Enable rip button
self.set_status("Disc data received")
self.button_rip.setEnabled(True)
def cddb_choose_dialog(self, what: int, info: dict):
if what == media.CALLBACK_CDDB_CHOICE:
values = tuple(info.values())
keys = tuple(info.keys())
dlg = SelectionDialog(self, "Multiple CDDB entries found. Choose one.", values)
if dlg.exec():
return keys[dlg.get_selected_index()]
else:
msg = "Invalid cddb selection!"
logger.error(msg)
self.set_status(msg)
QMessageBox.critical(self, "Error!", msg)
def get_job_data(self, list_index):
list_item = self.list_widget.item(list_index)
row_widget = self.list_widget.itemWidget(list_item)
track_info = {}
track_info[media.KEY_TRACK] = list_index + 1
for key in MEDIA_GUI_DICT:
track_info[MEDIA_GUI_DICT[key]] = self.get_album_field(key)
track_info[media.KEY_TITLE] = row_widget.text_edit.toPlainText()
return track_info
def get_rip_worker(self):
return RipQueueWorker(self.__basepath__)
def get_encode_worker(self):
return EncodeQueueWorker(self.__basepath__)
if __name__ == "__main__":
#
# Command line arguments
#
default_baspath = os.path.join(os.getenv("HOME"), "pyrip")
parser = argparse.ArgumentParser(description='Description')
parser.add_argument('-b', '--basepath', help=f'The rip and encode basepath (default is {default_baspath})', default=default_baspath)
args = parser.parse_args()
#
# Initialise GUI
#
app = QApplication(sys.argv)
window = RipMainWindow(args.basepath)
#
# Start media observer
#
context = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(context)
observer = MonitorObserver(monitor)
observer.deviceEvent.connect(window.device_callback)
observer.daemon = True
monitor.start()
#
# Start GUI
#
window.show()
sys.exit(app.exec())