Browse Source

get scsi drive for win32 burning

Noah Vogt 1 năm trước cách đây
mục cha
commit
ee2ff2e4ee
7 tập tin đã thay đổi với 175 bổ sung58 xóa
  1. 1 0
      config/default_config.py
  2. 1 1
      os_agnostic/__init__.py
  3. 87 1
      os_agnostic/cd.py
  4. 13 6
      recording/cd.py
  5. 1 50
      recording/gui.py
  6. 1 0
      utils/__init__.py
  7. 71 0
      utils/choose.py

+ 1 - 0
config/default_config.py

@@ -98,6 +98,7 @@ CD_RECORD_FFMPEG_INPUT_ARGS = ""
 CD_RECORD_MAX_SECONDS = 4800
 CD_RECORD_MIN_TRACK_MILIS = 4200
 CD_RECORD_PREFERED_DRIVE = ""
+CD_RECORD_PREFERED_SCSI_DRIVE = ""
 
 SERMON_UPLOAD_FTP_HOSTNAME = ""
 SERMON_UPLOAD_FTP_USER = ""

+ 1 - 1
os_agnostic/__init__.py

@@ -13,4 +13,4 @@
 # 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
+from .cd import eject_drive, get_cd_drives, get_cdrecord_devname

+ 87 - 1
os_agnostic/cd.py

@@ -13,14 +13,24 @@
 # 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
 import os
 
-from utils import log, CustomException
+from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
+    QApplication,
+    QMessageBox,
+    QDialog,
+)
+
+from utils import log, CustomException, RadioButtonDialog
 
 if os.name == "nt":
     # pylint: disable=import-error
     import wmi  # pyright: ignore
     import ctypes
+    from subprocess import PIPE, run
+    from re import match, search
+    import config as const
 else:
     from pycdio import DRIVER_DEVICE
     from cdio import (
@@ -30,6 +40,82 @@ else:
     )
 
 
+def choose_right_scsi_drive(drives: list) -> str:
+    if len(drives) != 1:
+        log("Warning: More than one SCSI drive found", color="yellow")
+        if (
+            # pylint: disable=possibly-used-before-assignment
+            const.CD_RECORD_PREFERED_SCSI_DRIVE in drives
+            and const.CD_RECORD_PREFERED_SCSI_DRIVE != ""
+        ):
+            return const.CD_RECORD_PREFERED_SCSI_DRIVE
+
+        # pylint: disable=possibly-used-before-assignment
+        dialog = RadioButtonDialog(drives, "Choose a SCSI Drive")
+        if dialog.exec_() == QDialog.Accepted:
+            print(f"Dialog accepted: {dialog.chosen}")
+            return dialog.chosen
+        log("Warning: Choosing first SCSI drive...", color="yellow")
+
+    return drives[0]
+
+
+def get_cdrecord_devname(cd_drive: str) -> str:
+    if os.name == "nt":
+        scsi_drives = get_scsi_drives()
+        return choose_right_scsi_drive(scsi_drives)
+
+    return cd_drive
+
+
+def bytes_line_ends_with(line: bytes, target: str) -> bool:
+    byte_len = len(line)
+    if byte_len < len(target):
+        return False
+
+    for index in range(-1, -1 * len(target), -1):
+        if ord(target[index]) != line[index]:
+            return False
+
+    return True
+
+
+def get_scsi_drives() -> list[str]:
+    # pylint: disable=possibly-used-before-assignment, subprocess-run-check
+    try:
+        drives = []
+        result = run(["cdrecord", "-scanbus"], stdout=PIPE)
+        output = result.stdout
+        if result.returncode != 0:
+            raise CustomException("Command 'cdrecord -scanbus' failed.")
+        for line in output.split(b"\n"):
+            print(line)
+            # pylint: disable=possibly-used-before-assignment
+            if (
+                match(rb"\s*[0-9](,[0-9])+\s*[0-9]+", line)
+                and (not bytes_line_ends_with(line, "*"))
+                and (not bytes_line_ends_with(line, "HOST ADAPTOR"))
+            ):
+                matches = search(rb"[0-9](,[0-9])+", line)
+                if matches is None:
+                    raise CustomException(f"could not parse line: '{line}'")
+                drives.append(matches.group().decode("ascii"))
+
+        if not drives:
+            raise CustomException("Could not find any SCSI drives")
+
+        return drives
+    except CustomException as error:
+        app = QApplication([])
+        QMessageBox.critical(
+            None,
+            "Error",
+            f"Could not get SCSI drives. Reason: {error}",
+        )
+        del app
+        sys.exit(1)
+
+
 def get_cd_drives() -> list[str]:
     if os.name == "nt":
         c = wmi.WMI()

+ 13 - 6
recording/cd.py

@@ -31,14 +31,20 @@ import config as const
 from input import (
     validate_cd_burn_config,
 )
-from utils import expand_dir, log, make_sure_file_exists, InfoMsgBox
-from os_agnostic import get_cd_drives, eject_drive
+from utils import (
+    expand_dir,
+    log,
+    make_sure_file_exists,
+    InfoMsgBox,
+    RadioButtonDialog,
+)
+from os_agnostic import get_cd_drives, eject_drive, get_cdrecord_devname
 from audio import AudioSourceFileType
 from .verify import (
     is_legal_sheet_filename,
     get_padded_cd_num_from_sheet_filename,
 )
-from .gui import RadioButtonDialog, WaveAndSheetPreviewChooserGUI
+from .gui import WaveAndSheetPreviewChooserGUI
 
 
 def get_burn_cmd(cd_drive: str, yyyy_mm_dd, padded_zfill_num: str) -> str:
@@ -48,8 +54,8 @@ def get_burn_cmd(cd_drive: str, yyyy_mm_dd, padded_zfill_num: str) -> str:
         f"sheet-{padded_zfill_num}.cue",
     )
     return (
-        f"cdrecord -pad dev={cd_drive} -dao -swab -text -audio "
-        + f"-cuefile='{cue_sheet_path}'"
+        f"cdrecord -pad dev={get_cdrecord_devname(cd_drive)} -dao -swab "
+        + f"-text -audio -cuefile='{cue_sheet_path}'"
     )
 
 
@@ -187,7 +193,8 @@ def burn_cds_of_day(yyyy_mm_dd: str) -> None:
                 log(f"Burning CD's from sheets: {chosen_sheets}")
                 num_of_chosen_sheets = len(dialog.chosen_audios)
                 for num, sheet in enumerate(chosen_sheets):
-                    del app  # pyright: ignore
+                    if num == 0:
+                        del app  # pyright: ignore
                     last_cd_to_burn = num == num_of_chosen_sheets
                     burn_and_eject_cd(
                         yyyy_mm_dd,

+ 1 - 50
recording/gui.py

@@ -25,11 +25,9 @@ from PyQt5.QtWidgets import (  # pylint: disable=no-name-in-module
     QHBoxLayout,
     QStyle,
     QLabel,
-    QRadioButton,
     QCheckBox,
     QPushButton,
     QMessageBox,
-    QButtonGroup,
     QScrollArea,
     QWidget,
 )
@@ -44,7 +42,7 @@ from PyQt5.QtCore import (  # pylint: disable=no-name-in-module
 )
 
 from audio import AudioSourceFileType, ChosenAudio, get_wave_duration_in_secs
-from utils import CustomException, log
+from utils import CustomException, log, RadioButtonDialog
 import config as const
 
 
@@ -61,53 +59,6 @@ class CheckBoxConstruct:
     check_box: QCheckBox
 
 
-class RadioButtonDialog(QDialog):  # pylint: disable=too-few-public-methods
-    def __init__(self, options: list[str], window_title: str):
-        super().__init__()
-        self.setWindowTitle(window_title)
-
-        self.master_layout = QVBoxLayout(self)
-
-        scroll_area_layout = self.get_scroll_area_layout()
-
-        self.radio_button_group = QButtonGroup(self)
-
-        self.chosen = ""
-        for num, item in enumerate(options):
-            radio_button = QRadioButton(item)
-            if num == 0:
-                radio_button.setChecked(True)
-            self.radio_button_group.addButton(radio_button)
-            scroll_area_layout.addWidget(radio_button)
-
-        ok_button = QPushButton("OK")
-        ok_button.clicked.connect(self.accept)
-        self.master_layout.addWidget(ok_button)
-
-    def get_scroll_area_layout(self):
-        scroll_area = QScrollArea()
-        scroll_area.setWidgetResizable(True)
-        self.master_layout.addWidget(scroll_area)
-
-        scroll_content = QWidget()
-        scroll_area.setWidget(scroll_content)
-        scroll_area_layout = QVBoxLayout(scroll_content)
-        return scroll_area_layout
-
-    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.",
-            )
 
 
 class WaveAndSheetPreviewChooserGUI(

+ 1 - 0
utils/__init__.py

@@ -24,3 +24,4 @@ from .create_min_obs_subdirs import create_min_obs_subdirs
 from .clear_obs_slides_dir import clear_obs_slides_dir
 from .path import expand_dir, make_sure_file_exists
 from .date import get_yyyy_mm_dd_date, get_unix_milis
+from .choose import RadioButtonDialog

+ 71 - 0
utils/choose.py

@@ -0,0 +1,71 @@
+# 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
+    QDialog,
+    QVBoxLayout,
+    QRadioButton,
+    QPushButton,
+    QMessageBox,
+    QButtonGroup,
+    QScrollArea,
+    QWidget,
+)
+
+
+class RadioButtonDialog(QDialog):  # pylint: disable=too-few-public-methods
+    def __init__(self, options: list[str], window_title: str):
+        super().__init__()
+        self.setWindowTitle(window_title)
+
+        self.master_layout = QVBoxLayout(self)
+
+        scroll_area_layout = self.get_scroll_area_layout()
+
+        self.radio_button_group = QButtonGroup(self)
+
+        self.chosen = ""
+        for num, item in enumerate(options):
+            radio_button = QRadioButton(item)
+            if num == 0:
+                radio_button.setChecked(True)
+            self.radio_button_group.addButton(radio_button)
+            scroll_area_layout.addWidget(radio_button)
+
+        ok_button = QPushButton("OK")
+        ok_button.clicked.connect(self.accept)
+        self.master_layout.addWidget(ok_button)
+
+    def get_scroll_area_layout(self):
+        scroll_area = QScrollArea()
+        scroll_area.setWidgetResizable(True)
+        self.master_layout.addWidget(scroll_area)
+
+        scroll_content = QWidget()
+        scroll_area.setWidget(scroll_content)
+        scroll_area_layout = QVBoxLayout(scroll_content)
+        return scroll_area_layout
+
+    def accept(self):
+        selected_button = self.radio_button_group.checkedButton()
+        if selected_button:
+            self.chosen = selected_button.text()
+            super().accept()
+        else:
+            QMessageBox.warning(
+                self,
+                "No Selection",
+                "Please select an option before proceeding.",
+            )