From 52642d693beef4674c7803e389dd38172d847831 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 11 Oct 2021 18:23:38 +1000 Subject: [PATCH] drop PyAudio support I do not recall anyone reporting that it worked better than the Qt implementation for them, and the lack of recent wheels on PyPI is a pain. We can always add it back in the future if enough people come out of the woodwork to report they were using it. --- qt/aqt/forms/preferences.ui | 8 --- qt/aqt/pinnedmodules.py | 1 - qt/aqt/preferences.py | 37 +----------- qt/aqt/profiles.py | 17 ------ qt/aqt/qt5.py | 6 -- qt/aqt/sound.py | 114 +++--------------------------------- 6 files changed, 8 insertions(+), 175 deletions(-) diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index e705d0b05..085c10881 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -107,13 +107,6 @@ - - - - - - - @@ -616,7 +609,6 @@ paste_strips_formatting nightMode useCurrent - recording_driver default_search_text uiScale showEstimates diff --git a/qt/aqt/pinnedmodules.py b/qt/aqt/pinnedmodules.py index 3586d4ff5..8fe9e2c39 100644 --- a/qt/aqt/pinnedmodules.py +++ b/qt/aqt/pinnedmodules.py @@ -33,7 +33,6 @@ except: import PyQt5.QtSvg # type: ignore import PyQt5.QtMultimedia # type: ignore import socks -import pyaudio # legacy compat import anki.storage diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index c0cce5612..e10029b94 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -9,7 +9,7 @@ from anki.collection import OpChanges from anki.consts import newCardSchedulingLabels from aqt import AnkiQt from aqt.operations.collection import set_preferences -from aqt.profiles import RecordingDriver, VideoDriver +from aqt.profiles import VideoDriver from aqt.qt import * from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr @@ -134,48 +134,13 @@ class Preferences(QDialog): def setup_profile(self) -> None: "Setup options stored in the user profile." - self.setup_recording_driver() self.setup_network() self.setup_backup() def update_profile(self) -> None: - self.update_recording_driver() self.update_network() self.update_backup() - # Profile: recording driver - ###################################################################### - - def setup_recording_driver(self) -> None: - self._recording_drivers = [ - RecordingDriver.QtAudioInput, - RecordingDriver.PyAudio, - ] - # The plan is to phase out PyAudio soon, so will hold off on - # making this string translatable for now. - self.form.recording_driver.addItems( - [ - f"Voice recording driver: {driver.value}" - for driver in self._recording_drivers - ] - ) - self.form.recording_driver.setCurrentIndex( - self._recording_drivers.index(self.mw.pm.recording_driver()) - ) - - def update_recording_driver(self) -> None: - new_audio_driver = self._recording_drivers[ - self.form.recording_driver.currentIndex() - ] - if self.mw.pm.recording_driver() != new_audio_driver: - self.mw.pm.set_recording_driver(new_audio_driver) - if new_audio_driver == RecordingDriver.PyAudio: - showInfo( - """\ -The PyAudio driver will likely be removed in a future update. If you find it works better \ -for you than the default driver, please let us know on the Anki forums.""" - ) - # Profile: network ###################################################################### diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index ce1fabef6..59ba16572 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -31,11 +31,6 @@ from aqt.utils import disable_help_button, showWarning, tr # - Saves in sqlite rather than a flat file so the config can't be corrupted -class RecordingDriver(Enum): - PyAudio = "PyAudio" - QtAudioInput = "Qt" - - class VideoDriver(Enum): OpenGL = "auto" ANGLE = "angle" @@ -556,18 +551,6 @@ create table if not exists profiles def set_auto_sync_media_minutes(self, val: int) -> None: self.profile["autoSyncMediaMinutes"] = val - def recording_driver(self) -> RecordingDriver: - if driver := self.profile.get("recordingDriver"): - try: - return RecordingDriver(driver) - except ValueError: - # revert to default - pass - return RecordingDriver.QtAudioInput - - def set_recording_driver(self, driver: RecordingDriver) -> None: - self.profile["recordingDriver"] = driver.value - def show_browser_table_tooltips(self) -> bool: return self.profile.get("browserTableTooltips", True) diff --git a/qt/aqt/qt5.py b/qt/aqt/qt5.py index 6c1e68f63..d87a3fa2c 100644 --- a/qt/aqt/qt5.py +++ b/qt/aqt/qt5.py @@ -96,9 +96,3 @@ class QtAudioInputRecorder(Recorder): t.timeout.connect(on_stop_timer) # type: ignore t.setSingleShot(True) t.start(500) - - -def prompt_for_mic_permission() -> None: - from PyQt5.QtMultimedia import QAudioDeviceInfo - - QAudioDeviceInfo.defaultInputDevice() diff --git a/qt/aqt/sound.py b/qt/aqt/sound.py index 3642a9157..ee6c38ae2 100644 --- a/qt/aqt/sound.py +++ b/qt/aqt/sound.py @@ -9,7 +9,6 @@ import platform import re import subprocess import sys -import threading import time import wave from abc import ABC, abstractmethod @@ -23,11 +22,9 @@ import aqt from anki import hooks from anki.cards import Card from anki.sound import AV_REF_RE, AVTag, SoundOrVideoTag -from anki.types import assert_exhaustive from anki.utils import isLin, isMac, isWin, namedtmp from aqt import gui_hooks from aqt.mpv import MPV, MPVBase, MPVCommandError -from aqt.profiles import RecordingDriver from aqt.qt import * from aqt.taskman import TaskManager from aqt.utils import ( @@ -546,16 +543,6 @@ class Recorder(ABC): ########################################################################## -def prompt_for_mic_permission() -> None: - if qtmajor == 5 and isMac: - from .qt5 import prompt_for_mic_permission - - prompt_for_mic_permission() - else: - # no longer seems to be required, perhaps due to newer macOS sdk? - pass - - class QtAudioInputRecorder(Recorder): def __init__(self, output_path: str, mw: aqt.AnkiQt, parent: QWidget) -> None: super().__init__(output_path) @@ -625,87 +612,6 @@ class QtAudioInputRecorder(Recorder): t.start(500) -# PyAudio recording -########################################################################## - -try: - import pyaudio -except: - pyaudio = None - - -PYAU_CHANNELS = 1 -PYAU_INPUT_INDEX: int | None = None - - -class PyAudioThreadedRecorder(threading.Thread): - def __init__(self, output_path: str, startup_delay: float) -> None: - threading.Thread.__init__(self) - self._output_path = output_path - self._startup_delay = startup_delay - self.finish = False - # though we're using pyaudio here, we rely on Qt to trigger - # the permission prompt - prompt_for_mic_permission() - - def run(self) -> None: - chunk = 1024 - p = pyaudio.PyAudio() - - rate = int(p.get_default_input_device_info()["defaultSampleRate"]) - PYAU_FORMAT = pyaudio.paInt16 - - stream = p.open( - format=PYAU_FORMAT, - channels=PYAU_CHANNELS, - rate=rate, - input=True, - input_device_index=PYAU_INPUT_INDEX, - frames_per_buffer=chunk, - ) - - # swallow the first 300ms to allow audio device to quiesce - wait = int(rate * self._startup_delay) - stream.read(wait, exception_on_overflow=False) - - # read data in a loop until self.finish is set - data = b"" - while not self.finish: - data += stream.read(chunk, exception_on_overflow=False) - - # write out the wave file - stream.close() - p.terminate() - wf = wave.open(self._output_path, "wb") - wf.setnchannels(PYAU_CHANNELS) - wf.setsampwidth(p.get_sample_size(PYAU_FORMAT)) - wf.setframerate(rate) - wf.writeframes(data) - wf.close() - - -class PyAudioRecorder(Recorder): - def __init__(self, mw: aqt.AnkiQt, output_path: str) -> None: - super().__init__(output_path) - self.mw = mw - - def start(self, on_done: Callable[[], None]) -> None: - self.thread = PyAudioThreadedRecorder(self.output_path, self.STARTUP_DELAY) - self.thread.start() - super().start(on_done) - - def stop(self, on_done: Callable[[str], None]) -> None: - # ensure at least a second captured - while self.duration() < 1: - time.sleep(0.1) - - def func(fut: Future) -> None: - Recorder.stop(self, on_done) - - self.thread.finish = True - self.mw.taskman.run_in_background(self.thread.join, func) - - # Recording dialog ########################################################################## @@ -760,20 +666,14 @@ class RecordDialog(QDialog): saveGeom(self, "audioRecorder2") def _start_recording(self) -> None: - driver = self.mw.pm.recording_driver() - if driver is RecordingDriver.PyAudio: - self._recorder = PyAudioRecorder(self.mw, namedtmp("rec.wav")) - elif driver is RecordingDriver.QtAudioInput: - if qtmajor > 5: - self._recorder = QtAudioInputRecorder( - namedtmp("rec.wav"), self.mw, self._parent - ) - else: - from .qt5 import QtAudioInputRecorder as Qt5Recorder - - self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent) + if qtmajor > 5: + self._recorder = QtAudioInputRecorder( + namedtmp("rec.wav"), self.mw, self._parent + ) else: - assert_exhaustive(driver) + from .qt5 import QtAudioInputRecorder as Qt5Recorder + + self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent) self._recorder.start(self._start_timer) def _start_timer(self) -> None: