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:
parent
caa76c8b96
commit
52642d693b
@ -107,13 +107,6 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="recording_driver">
|
||||
<property name="currentText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="search_text_label">
|
||||
<property name="text">
|
||||
@ -616,7 +609,6 @@
|
||||
<tabstop>paste_strips_formatting</tabstop>
|
||||
<tabstop>nightMode</tabstop>
|
||||
<tabstop>useCurrent</tabstop>
|
||||
<tabstop>recording_driver</tabstop>
|
||||
<tabstop>default_search_text</tabstop>
|
||||
<tabstop>uiScale</tabstop>
|
||||
<tabstop>showEstimates</tabstop>
|
||||
|
@ -33,7 +33,6 @@ except:
|
||||
import PyQt5.QtSvg # type: ignore
|
||||
import PyQt5.QtMultimedia # type: ignore
|
||||
import socks
|
||||
import pyaudio
|
||||
|
||||
# legacy compat
|
||||
import anki.storage
|
||||
|
@ -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
|
||||
######################################################################
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
100
qt/aqt/sound.py
100
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,10 +666,6 @@ 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
|
||||
@ -772,8 +674,6 @@ class RecordDialog(QDialog):
|
||||
from .qt5 import QtAudioInputRecorder as Qt5Recorder
|
||||
|
||||
self._recorder = Qt5Recorder(namedtmp("rec.wav"), self.mw, self._parent)
|
||||
else:
|
||||
assert_exhaustive(driver)
|
||||
self._recorder.start(self._start_timer)
|
||||
|
||||
def _start_timer(self) -> None:
|
||||
|
Loading…
Reference in New Issue
Block a user