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.
This commit is contained in:
Damien Elmes 2021-10-11 18:23:38 +10:00
parent caa76c8b96
commit 52642d693b
6 changed files with 8 additions and 175 deletions

View File

@ -107,13 +107,6 @@
</item> </item>
</widget> </widget>
</item> </item>
<item>
<widget class="QComboBox" name="recording_driver">
<property name="currentText">
<string notr="true"/>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel" name="search_text_label"> <widget class="QLabel" name="search_text_label">
<property name="text"> <property name="text">
@ -616,7 +609,6 @@
<tabstop>paste_strips_formatting</tabstop> <tabstop>paste_strips_formatting</tabstop>
<tabstop>nightMode</tabstop> <tabstop>nightMode</tabstop>
<tabstop>useCurrent</tabstop> <tabstop>useCurrent</tabstop>
<tabstop>recording_driver</tabstop>
<tabstop>default_search_text</tabstop> <tabstop>default_search_text</tabstop>
<tabstop>uiScale</tabstop> <tabstop>uiScale</tabstop>
<tabstop>showEstimates</tabstop> <tabstop>showEstimates</tabstop>

View File

@ -33,7 +33,6 @@ except:
import PyQt5.QtSvg # type: ignore import PyQt5.QtSvg # type: ignore
import PyQt5.QtMultimedia # type: ignore import PyQt5.QtMultimedia # type: ignore
import socks import socks
import pyaudio
# legacy compat # legacy compat
import anki.storage import anki.storage

View File

@ -9,7 +9,7 @@ from anki.collection import OpChanges
from anki.consts import newCardSchedulingLabels from anki.consts import newCardSchedulingLabels
from aqt import AnkiQt from aqt import AnkiQt
from aqt.operations.collection import set_preferences from aqt.operations.collection import set_preferences
from aqt.profiles import RecordingDriver, VideoDriver from aqt.profiles import VideoDriver
from aqt.qt import * from aqt.qt import *
from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr
@ -134,48 +134,13 @@ class Preferences(QDialog):
def setup_profile(self) -> None: def setup_profile(self) -> None:
"Setup options stored in the user profile." "Setup options stored in the user profile."
self.setup_recording_driver()
self.setup_network() self.setup_network()
self.setup_backup() self.setup_backup()
def update_profile(self) -> None: def update_profile(self) -> None:
self.update_recording_driver()
self.update_network() self.update_network()
self.update_backup() 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 # Profile: network
###################################################################### ######################################################################

View File

@ -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 # - 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): class VideoDriver(Enum):
OpenGL = "auto" OpenGL = "auto"
ANGLE = "angle" ANGLE = "angle"
@ -556,18 +551,6 @@ create table if not exists profiles
def set_auto_sync_media_minutes(self, val: int) -> None: def set_auto_sync_media_minutes(self, val: int) -> None:
self.profile["autoSyncMediaMinutes"] = val 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: def show_browser_table_tooltips(self) -> bool:
return self.profile.get("browserTableTooltips", True) return self.profile.get("browserTableTooltips", True)

View File

@ -96,9 +96,3 @@ class QtAudioInputRecorder(Recorder):
t.timeout.connect(on_stop_timer) # type: ignore t.timeout.connect(on_stop_timer) # type: ignore
t.setSingleShot(True) t.setSingleShot(True)
t.start(500) t.start(500)
def prompt_for_mic_permission() -> None:
from PyQt5.QtMultimedia import QAudioDeviceInfo
QAudioDeviceInfo.defaultInputDevice()

View File

@ -9,7 +9,6 @@ import platform
import re import re
import subprocess import subprocess
import sys import sys
import threading
import time import time
import wave import wave
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@ -23,11 +22,9 @@ import aqt
from anki import hooks from anki import hooks
from anki.cards import Card from anki.cards import Card
from anki.sound import AV_REF_RE, AVTag, SoundOrVideoTag from anki.sound import AV_REF_RE, AVTag, SoundOrVideoTag
from anki.types import assert_exhaustive
from anki.utils import isLin, isMac, isWin, namedtmp from anki.utils import isLin, isMac, isWin, namedtmp
from aqt import gui_hooks from aqt import gui_hooks
from aqt.mpv import MPV, MPVBase, MPVCommandError from aqt.mpv import MPV, MPVBase, MPVCommandError
from aqt.profiles import RecordingDriver
from aqt.qt import * from aqt.qt import *
from aqt.taskman import TaskManager from aqt.taskman import TaskManager
from aqt.utils import ( 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): class QtAudioInputRecorder(Recorder):
def __init__(self, output_path: str, mw: aqt.AnkiQt, parent: QWidget) -> None: def __init__(self, output_path: str, mw: aqt.AnkiQt, parent: QWidget) -> None:
super().__init__(output_path) super().__init__(output_path)
@ -625,87 +612,6 @@ class QtAudioInputRecorder(Recorder):
t.start(500) 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 # Recording dialog
########################################################################## ##########################################################################
@ -760,10 +666,6 @@ class RecordDialog(QDialog):
saveGeom(self, "audioRecorder2") saveGeom(self, "audioRecorder2")
def _start_recording(self) -> None: 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: if qtmajor > 5:
self._recorder = QtAudioInputRecorder( self._recorder = QtAudioInputRecorder(
namedtmp("rec.wav"), self.mw, self._parent namedtmp("rec.wav"), self.mw, self._parent
@ -772,8 +674,6 @@ class RecordDialog(QDialog):
from .qt5 import QtAudioInputRecorder as Qt5Recorder from .qt5 import QtAudioInputRecorder as Qt5Recorder
self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent) self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent)
else:
assert_exhaustive(driver)
self._recorder.start(self._start_timer) self._recorder.start(self._start_timer)
def _start_timer(self) -> None: def _start_timer(self) -> None: