ソースを参照

made prototypes of cd burning dialogs

Noah Vogt 1 年間 前
コミット
501e229f1f
11 ファイル変更348 行追加204 行削除
  1. 0 132
      burn_cd_dialog.py
  2. 23 0
      burn_cd_of_today.py
  3. 40 70
      choose_cd_dialog.py
  4. 1 0
      config/default_config.py
  5. 1 0
      input/__init__.py
  6. 94 0
      input/gui.py
  7. 18 0
      os_agnostic/__init__.py
  8. 53 0
      os_agnostic/cd.py
  9. 3 0
      pyrightconfig.json
  10. 1 0
      utils/__init__.py
  11. 114 2
      utils/scripts.py

+ 0 - 132
burn_cd_dialog.py

@@ -1,132 +0,0 @@
-#!/usr/bin/env python3
-
-"""
-Copyright © 2024 Noah Vogt <noah@noahvogt.com>
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-"""
-
-import sys
-from subprocess import Popen
-from shlex import split
-
-from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
-    QApplication,
-    QMessageBox,
-)
-from PyQt5.QtCore import QTimer  # pylint: disable=no-name-in-module
-
-from pycdio import DRIVER_DEVICE
-from cdio import get_devices, Device, DriverUnsupportedError, DeviceException
-
-from utils import (
-    make_sure_file_exists,
-    log,
-)
-from input import validate_cd_record_config
-import config as const
-
-
-class InfoMsgBox:
-    def __init__(self, icon: QMessageBox.Icon, title: str, text: str) -> None:
-        self.app = QApplication([])
-        self.title = title
-        self.text = text
-        self.icon = icon
-        self.show_msg_box()
-        self.app.exec_()
-
-    def show_msg_box(self):
-        self.message_box = QMessageBox()
-        self.message_box.setIcon(self.icon)
-        self.message_box.setWindowTitle(self.title)
-        self.message_box.setText(self.text)
-
-        self.message_box.show()
-
-
-class CDBurnerGUI:
-    def __init__(self, drive: Device):
-        self.app = QApplication([])
-        self.drive = drive
-        self.exit_code = 1
-        self.show_burning_msg_box()
-        self.start_burn_subprocess()
-        self.app.exec_()
-
-    def burning_successful(self) -> bool:
-        if self.exit_code == 0:
-            return True
-        return False
-
-    def show_burning_msg_box(self):
-        self.message_box = QMessageBox()
-        self.message_box.setWindowTitle("Info")
-        self.message_box.setText("Burning CD...")
-        self.message_box.setInformativeText(
-            "Please wait for a few minutes. You can close this Window, as "
-            + "there will spawn another window after the operation is "
-            + "finished."
-        )
-
-        self.message_box.show()
-
-    def start_burn_subprocess(self):
-        process = Popen(["grep", "-a"])
-
-        while process.poll() is None:
-            QApplication.processEvents()
-        self.message_box.accept()
-
-        # Yeah this is hacky but it doesn't work when calling quit directly
-        QTimer.singleShot(0, self.app.quit)
-        self.exit_code = process.returncode
-
-
-def get_cd_drives() -> list:
-    cd_drives = get_devices(DRIVER_DEVICE)
-    return cd_drives
-
-
-def eject_drive(drive: Device) -> None:
-    try:
-        drive.eject_media()
-    except (DriverUnsupportedError, DeviceException):
-        log(f"Eject of CD-ROM drive {drive} failed")
-
-
-if __name__ == "__main__":
-    validate_cd_record_config()
-    make_sure_file_exists(const.CD_RECORD_CACHEFILE)
-
-    drives = get_cd_drives()
-    if not drives:
-        InfoMsgBox(
-            QMessageBox.Critical,
-            "Error",
-            "Error: Could not find a CD-ROM. Please try again",
-        )
-        sys.exit(1)
-    if len(drives) != 1:
-        # TODO: let user choose between drive slots / letters / devices
-        log("Warning: More than one cd drive found", color="yellow")
-        drives = drives[0]
-
-    BURN_SUCESS = CDBurnerGUI(drives[0]).burning_successful()
-    if BURN_SUCESS:
-        InfoMsgBox(QMessageBox.Info, "Info", "Successfully burned CD.")
-    else:
-        InfoMsgBox(QMessageBox.Critical, "Error", "Error: Failed to burn CD.")
-
-    eject_drive(drives[0])

+ 23 - 0
burn_cd_of_today.py

@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+
+"""
+Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from utils import burn_cd_of_day, get_yyyy_mm_dd_date
+
+if __name__ == "__main__":
+    burn_cd_of_day(get_yyyy_mm_dd_date())

+ 40 - 70
choose_cd_dialog.py

@@ -17,92 +17,62 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
+import sys
+from os import listdir
+
 from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
     QApplication,
     QDialog,
-    QVBoxLayout,
-    QRadioButton,
-    QPushButton,
     QMessageBox,
-    QButtonGroup,
-    QScrollArea,
-    QWidget,
 )
 
-from PyQt5.QtGui import QColor, QIcon  # pylint: disable=all
-
 from utils import (
-    get_yyyy_mm_dd_date,
-    make_sure_file_exists,
-    is_valid_cd_record_checkfile,
+    burn_cd_of_day,
     log,
 )
-from input import get_cachefile_content, validate_cd_record_config
+from input import (
+    validate_cd_record_config,
+    RadioButtonDialog,
+    InfoMsgBox,
+)
 import config as const
 
 
-def stop_cd_recording() -> None:
-    cachefile_content = get_cachefile_content(const.CD_RECORD_CACHEFILE)
-    yyyy_mm_dd = get_yyyy_mm_dd_date()
-
-    if is_valid_cd_record_checkfile(cachefile_content, yyyy_mm_dd):
+def choose_cd() -> list[str]:
+    # pylint: disable=unused-variable
+    app = QApplication([])
+    try:
+        dirs = sorted(listdir(const.CD_RECORD_OUTPUT_BASEDIR))
+        dirs.reverse()
+
+        if not dirs:
+            return [
+                f"Did not find any CD's in: {const.CD_RECORD_OUTPUT_BASEDIR}.",
+                "",
+            ]
+
+        dialog = RadioButtonDialog(dirs, "Choose a CD to Burn")
+        if dialog.exec_() == QDialog.Accepted:
+            log(f"Burning CD for day: {dialog.chosen}")
+            return ["", dialog.chosen]
+        return ["ignore", ""]
+    except (FileNotFoundError, PermissionError, IOError):
         pass
 
+    return [
+        f"Failed to access directory: {const.CD_RECORD_OUTPUT_BASEDIR}.",
+        "",
+    ]
 
-class RadioButtonDialog(QDialog):
-    def __init__(self):
-        super().__init__()
-        self.setWindowTitle("Choose a CD to Burn")
-
-        master_layout = QVBoxLayout(self)
-
-        scroll_area = QScrollArea()
-        scroll_area.setWidgetResizable(True)
-        master_layout.addWidget(scroll_area)
-
-        scroll_content = QWidget()
-        scroll_area.setWidget(scroll_content)
-        scroll_area_layout = QVBoxLayout(scroll_content)
-
-        self.radio_button_group = QButtonGroup(self)
-
-        self.radio_buttons = []
-        for i in range(1, 101):
-            radio_button = QRadioButton(f"Radio Button {i}")
-            if i == 1:
-                radio_button.setChecked(True)
-            self.radio_buttons.append(radio_button)
-            self.radio_button_group.addButton(radio_button)
-            scroll_area_layout.addWidget(radio_button)
-
-        ok_button = QPushButton("OK")
-        ok_button.clicked.connect(self.accept)
-        master_layout.addWidget(ok_button)
-
-    def accept(self):
-        selected_button = self.radio_button_group.checkedButton()
-        if selected_button:
-            QMessageBox.information(
-                self, "Selection", f"You selected: {selected_button.text()}"
-            )
-            super().accept()
-        else:
-            QMessageBox.warning(
-                self,
-                "No Selection",
-                "Please select an option before proceeding.",
-            )
-
-
-def main() -> None:
-    validate_cd_record_config()
-    make_sure_file_exists(const.CD_RECORD_CACHEFILE)
 
-    app = QApplication([])
-    dialog = RadioButtonDialog()
-    if dialog.exec_() == QDialog.Accepted:
-        print("Dialog accepted.")
+def choose_and_burn_cd():
+    msg, yyyy_mm_dd = choose_cd()
+    if msg == "":
+        burn_cd_of_day(yyyy_mm_dd)
+    elif msg != "ignore":
+        InfoMsgBox(QMessageBox.Critical, "Error", msg)
 
 
 if __name__ == "__main__":
-    main()
+    validate_cd_record_config()
+    choose_and_burn_cd()

+ 1 - 0
config/default_config.py

@@ -99,3 +99,4 @@ CD_RECORD_OUTPUT_BASEDIR = ""
 CD_RECORD_FFMPEG_INPUT_ARGS = ""
 CD_RECORD_MAX_SECONDS = 4800
 CD_RECORD_MIN_TRACK_MILIS = 4200
+CD_RECORD_PREFERED_DRIVE = ""

+ 1 - 0
input/__init__.py

@@ -32,3 +32,4 @@ from .validate_config import (
     validate_cd_record_config,
 )
 from .slide_selection_iterator import slide_selection_iterator
+from .gui import RadioButtonDialog, InfoMsgBox

+ 94 - 0
input/gui.py

@@ -0,0 +1,94 @@
+"""
+Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
+    QApplication,
+    QDialog,
+    QVBoxLayout,
+    QRadioButton,
+    QPushButton,
+    QMessageBox,
+    QButtonGroup,
+    QScrollArea,
+    QWidget,
+)
+
+
+# pylint: disable=too-few-public-methods
+class InfoMsgBox:
+    def __init__(self, icon: QMessageBox.Icon, title: str, text: str) -> None:
+        self.app = QApplication([])
+        self.title = title
+        self.text = text
+        self.icon = icon
+        self.show_msg_box()
+        self.app.exec_()
+
+    def show_msg_box(self):
+        self.message_box = QMessageBox()
+        self.message_box.setIcon(self.icon)
+        self.message_box.setWindowTitle(self.title)
+        self.message_box.setText(self.text)
+
+        self.message_box.show()
+
+
+class RadioButtonDialog(QDialog):  # pylint: disable=too-few-public-methods
+    def __init__(self, options: list[str], window_title: str):
+        super().__init__()
+        self.setWindowTitle(window_title)
+
+        master_layout = QVBoxLayout(self)
+
+        scroll_area = QScrollArea()
+        scroll_area.setWidgetResizable(True)
+        master_layout.addWidget(scroll_area)
+
+        scroll_content = QWidget()
+        scroll_area.setWidget(scroll_content)
+        scroll_area_layout = QVBoxLayout(scroll_content)
+
+        self.radio_button_group = QButtonGroup(self)
+
+        self.chosen = ""
+        self.radio_buttons = []
+        for num, item in enumerate(options):
+            radio_button = QRadioButton(item)
+            if num == 0:
+                radio_button.setChecked(True)
+            self.radio_buttons.append(radio_button)
+            self.radio_button_group.addButton(radio_button)
+            scroll_area_layout.addWidget(radio_button)
+
+        ok_button = QPushButton("OK")
+        ok_button.clicked.connect(self.accept)
+        master_layout.addWidget(ok_button)
+
+    def accept(self):
+        selected_button = self.radio_button_group.checkedButton()
+        if selected_button:
+            self.chosen = selected_button.text()
+            # QMessageBox.information(
+            #     self, "Selection", f"You selected: {selected_button.text()}"
+            # )
+            super().accept()
+        else:
+            QMessageBox.warning(
+                self,
+                "No Selection",
+                "Please select an option before proceeding.",
+            )

+ 18 - 0
os_agnostic/__init__.py

@@ -0,0 +1,18 @@
+"""
+Copyright © 2022 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+from .cd import eject_drive, get_cd_drives

+ 53 - 0
os_agnostic/cd.py

@@ -0,0 +1,53 @@
+"""
+Copyright © 2022 Noah Vogt <noah@noahvogt.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+
+import os
+
+from utils import log
+
+if os.name == "nt":
+    # pylint: disable=import-error
+    import wmi  # pyright: ignore
+else:
+    from pycdio import DRIVER_DEVICE
+    from cdio import (
+        get_devices,
+        Device,
+        DriverUnsupportedError,
+        DeviceException,
+    )
+
+
+def get_cd_drives() -> list:
+    if os.name == "nt":
+        c = wmi.WMI()
+        cd_drives = []
+        for cd in c.Win32_CDROMDrive():
+            cd_drives.append(cd.Drive)
+    # pylint: disable=possibly-used-before-assignment
+    else:
+        cd_drives = get_devices(DRIVER_DEVICE)
+    return cd_drives
+
+
+# pylint: disable=possibly-used-before-assignment
+def eject_drive(drive: Device) -> None:  # pyright: ignore
+    try:
+        drive.eject_media()  # pyright: ignore
+    # pylint: disable=possibly-used-before-assignment
+    except (DriverUnsupportedError, DeviceException):
+        log(f"Eject of CD-ROM drive {drive} failed")  # pyright: ignore

+ 3 - 0
pyrightconfig.json

@@ -0,0 +1,3 @@
+{
+    "pythonPlatform": "All",
+}

+ 1 - 0
utils/__init__.py

@@ -30,4 +30,5 @@ from .scripts import (
     make_sure_file_exists,
     switch_to_song,
     is_valid_cd_record_checkfile,
+    burn_cd_of_day,
 )

+ 114 - 2
utils/scripts.py

@@ -15,13 +15,35 @@ You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 """
 
+import sys
 from os import path
+from subprocess import Popen
+from shlex import split
 from time import sleep
 from re import match
 
 from pyautogui import keyDown, keyUp
-
-from utils import error_msg, log, get_yyyy_mm_dd_date
+from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
+    QApplication,
+    QMessageBox,
+)
+from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
+    QDialog,
+)
+from PyQt5.QtCore import QTimer  # pylint: disable=no-name-in-module
+
+from utils import (
+    log,
+    error_msg,
+    get_yyyy_mm_dd_date,
+    expand_dir,
+)
+from input import (
+    validate_cd_record_config,
+    RadioButtonDialog,
+    InfoMsgBox,
+)
+from os_agnostic import get_cd_drives, eject_drive
 import config as const
 
 
@@ -40,6 +62,34 @@ def make_sure_file_exists(cachefile: str) -> None:
             )
 
 
+def choose_right_cd_drive(drives: list) -> str:
+    if len(drives) != 1:
+        log("Warning: More than one cd drive found", color="yellow")
+        if (
+            const.CD_RECORD_PREFERED_DRIVE in drives
+            and const.CD_RECORD_PREFERED_DRIVE != ""
+        ):
+            return const.CD_RECORD_PREFERED_DRIVE
+
+        dialog = RadioButtonDialog(drives, "Choose a CD to Burn")
+        if dialog.exec_() == QDialog.Accepted:
+            print(f"Dialog accepted: {dialog.chosen}")
+            return dialog.chosen
+        log("Warning: Choosing first cd drive...", color="yellow")
+
+    return drives[0]
+
+
+def get_burn_cmd(cd_drive: str, yyyy_mm_dd) -> str:
+    cue_sheet_path = path.join(
+        expand_dir(const.CD_RECORD_OUTPUT_BASEDIR), yyyy_mm_dd, "sheet.cue"
+    )
+    return (
+        f"cdrecord -pad dev={cd_drive} -dao -swab -text -audio "
+        + f"-cuefile={cue_sheet_path}"
+    )
+
+
 def switch_to_song(song_number: int) -> None:
     if song_number > const.OBS_MIN_SUBDIRS:
         song_number = 1
@@ -96,3 +146,65 @@ def is_valid_cd_record_checkfile(
         # date matches today
         and cachefile_content[0].strip() == yyyy_mm_dd
     )
+
+
+class CDBurnerGUI:
+    def __init__(self, cd_drive: str, yyyy_mm_dd: str):
+        self.app = QApplication([])
+        self.drive = cd_drive
+        self.yyyy_mm_dd = yyyy_mm_dd
+        self.exit_code = 1
+        self.show_burning_msg_box()
+        self.start_burn_subprocess()
+        self.app.exec_()
+
+    def burning_successful(self) -> bool:
+        if self.exit_code == 0:
+            return True
+        return False
+
+    def show_burning_msg_box(self):
+        self.message_box = QMessageBox()
+        self.message_box.setWindowTitle("Info")
+        self.message_box.setText("Burning CD...")
+        self.message_box.setInformativeText(
+            "Please wait for a few minutes. You can close this Window, as "
+            + "there will spawn another window after the operation is "
+            + "finished."
+        )
+
+        self.message_box.show()
+
+    def start_burn_subprocess(self):
+        process = Popen(split(get_burn_cmd(self.drive, self.yyyy_mm_dd)))
+
+        while process.poll() is None:
+            QApplication.processEvents()
+        self.message_box.accept()
+
+        # Yeah this is hacky but it doesn't work when calling quit directly
+        QTimer.singleShot(0, self.app.quit)
+        self.exit_code = process.returncode
+
+
+def burn_cd_of_day(yyyy_mm_dd: str) -> None:
+    validate_cd_record_config()
+    make_sure_file_exists(const.CD_RECORD_CACHEFILE)
+
+    cd_drives = get_cd_drives()
+    if not cd_drives:
+        InfoMsgBox(
+            QMessageBox.Critical,
+            "Error",
+            "Error: Could not find a CD-ROM. Please try again",
+        )
+        sys.exit(1)
+    drive = choose_right_cd_drive(cd_drives)
+
+    burn_success = CDBurnerGUI(drive, yyyy_mm_dd).burning_successful()
+    if burn_success:
+        InfoMsgBox(QMessageBox.Info, "Info", "Successfully burned CD.")
+    else:
+        InfoMsgBox(QMessageBox.Critical, "Error", "Error: Failed to burn CD.")
+
+    eject_drive(drive)