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())