class QtRecorder(Recorder): def __init__(self, output_path: str, parent: QWidget): super().__init__(output_path) from PyQt5.QtMultimedia import QAudioRecorder self._recorder = QAudioRecorder(parent) audio = self._recorder.audioSettings() audio.setSampleRate(44100) audio.setChannelCount(1) self._recorder.setEncodingSettings( audio, self._recorder.videoSettings(), "audio/x-wav", ) self._recorder.setOutputLocation(QUrl.fromLocalFile(self.output_path)) self._recorder.setMuted(True) def start(self, on_done: Callable[[], None]) -> None: self._recorder.record() super().start(on_done) def stop(self, on_done: Callable[[str], None]): self._recorder.stop() super().stop(on_done) def on_timer(self): duration = self._recorder.duration() if duration >= 300: # disable mute after recording starts to avoid clicks/pops if self._recorder.isMuted(): self._recorder.setMuted(False)
class QVoiceRecorder(object): def __init__(self): pass def initAudioInput(self, filepath): self.recorder = QAudioRecorder() self.settings = QAudioEncoderSettings() self.settings.setCodec("audio/vorbis") self.recorder.setContainerFormat("ogg") #self.settings.setQuality(QtMultimedia.HighQuality) self.recorder.setEncodingSettings(self.settings) url = QtCore.QUrl.fromLocalFile( QtCore.QFileInfo(filepath).absoluteFilePath()) self.recorder.setOutputLocation(url) def start(self): self.recorder.record() def stop(self): self.recorder.pause() self.recorder.stop() self.recorder.stop()
class RecordAudio(object): recorder = None def __init__(self): pass def record(self, filename): url = QtCore.QUrl.fromLocalFile( QtCore.QFileInfo(filename).absoluteFilePath()) #content = QMediaObject(url) #self.recorder = QAudioRecorder() #source = QAudioInput() #source = QMediaObject() self.recorder = QAudioRecorder() settings = QAudioEncoderSettings() settings.setChannelCount(1) settings.setSampleRate(44100) settings.setBitRate(32) settings.setCodec("audio/vorbis") #settings.setEncodingMode(QtMultimedia.ConstantQualityEnconding) self.recorder.setContainerFormat("ogg") self.recorder.setEncodingSettings(settings) self.recorder.setOutputLocation(url) #container = None #self.recorder.setEncodingSettings(settings, container) def record(self): self.recorder.record() def stop(self): self.recorder.stop()
class Tuner(QMainWindow): def __init__(self, parent=None): super(Tuner, self).__init__(parent) # 从文件中加载UI定义 # self.ui = loadUi('Tuner.ui') #pyqt5 self.ui = Ui_MainWindow() # 创建UI对象 self.ui.setupUi(self) # 构造UI界面 # # 播放标准音 self.mediaPlayer = QMediaPlayer(self) self.ui.c1.clicked.connect(self.playaudioc) self.ui.d1.clicked.connect(self.playaudiod) self.ui.e1.clicked.connect(self.playaudioe) self.ui.f1.clicked.connect(self.playaudiof) self.ui.g1.clicked.connect(self.playaudiog) self.ui.a1.clicked.connect(self.playaudioa) self.ui.b1.clicked.connect(self.playaudiob) # 录音 self.fileName = "" self.recorder = QAudioRecorder(self) self.recorder.stateChanged.connect(self.do_stateChanged) self.recorder.durationChanged.connect(self.do_durationChanged) self.probe = QAudioProbe(self) # 探测器 self.probe.setSource(self.recorder) # self.probe.audioBufferProbed.connect(self.do_processBuffer) if self.recorder.defaultAudioInput() == "": # str类型 return # 无音频录入设备 for device in self.recorder.audioInputs(): self.ui.comboDevices.addItem(device) # 音频录入设备列表 for codecName in self.recorder.supportedAudioCodecs(): self.ui.comboCodec.addItem(codecName) # 支持的音频编码 sampleList, isContinuous = self.recorder.supportedAudioSampleRates() # isContinuous 是否支持连续的采样率,与C++不一样 for i in range(len(sampleList)): self.ui.comboSampleRate.addItem("%d" % sampleList[i]) # 支持的采样率 ## channels self.ui.comboChannels.addItem("1") self.ui.comboChannels.addItem("2") self.ui.comboChannels.addItem("4") ## ==============自定义功能函数============ def __setRecordParams(self): ##设置音频输入参数 selectedFile = self.ui.editOutputFile.text().strip() if (selectedFile == ""): QMessageBox.critical(self, "错误", "请先设置录音输出文件") return False if os.path.exists(selectedFile): os.remove(selectedFile) # 删除已有文件 ## QMessageBox.critical(self,"错误","录音输出文件被占用,无法删除") ## return False recordFile = QUrl.fromLocalFile(selectedFile) self.recorder.setOutputLocation(recordFile) # 设置输出文件 recordDevice = self.ui.comboDevices.currentText() self.recorder.setAudioInput(recordDevice) # 设置录入设备 settings = QAudioEncoderSettings() # 音频编码设置 settings.setCodec(self.ui.comboCodec.currentText()) # 编码 sampRate = int(self.ui.comboSampleRate.currentText()) settings.setSampleRate(sampRate) # 采样率 channelCount = int(self.ui.comboChannels.currentText()) settings.setChannelCount(channelCount) # 通道数 settings.setEncodingMode(QMultimedia.ConstantBitRateEncoding) # 固定比特率 self.recorder.setAudioSettings(settings) # 音频设置 return True def pitch_estimation(self, wavpath): if os.path.exists(wavpath): y, sr = librosa.load(wavpath) f0, voiced_flag, voiced_probs = librosa.pyin( y, fmin=librosa.note_to_hz('B3'), fmax=librosa.note_to_hz('C5')) f0 = f0[~np.isnan(f0)] times = librosa.times_like(f0) level = optimize.curve_fit(lambda x, b: b, times, np.nan_to_num(f0))[0] pitch = np.around(level[0], decimals=3).astype(float) return pitch ## ==========由connectSlotsByName() 自动连接的槽函数================== @pyqtSlot() def on_btnGetFile_clicked(self): ##"录音输出文件"按钮 curPath = os.getcwd() # 获取系统当前目录 dlgTitle = "选择输出文件" filt = "wav文件(*.wav)" self.fileName, flt, = QFileDialog.getSaveFileName( self, dlgTitle, curPath, filt) if (self.fileName != ""): self.ui.editOutputFile.setText(self.fileName) @pyqtSlot() ##开始录音 def on_actRecord_triggered(self): success = True if (self.recorder.state() == QMediaRecorder.StoppedState): # 已停止,重新设置 success = self.__setRecordParams() # 设置录音参数 if success: self.recorder.record() @pyqtSlot() ##退出 def on_actQuit_triggered(self): sys.exit(app.exec_()) # @pyqtSlot() ##暂停 # def on_actPause_triggered(self): # self.recorder.pause() @pyqtSlot() ##停止 def on_actStop_triggered(self): self.recorder.stop() # 录完立马分析并显示Hz name = self.fileName.split('/')[-1][:-4] # 存储的文件名 无.wav # func = lambda x, b: b if name == 'c1': # if os.path.exists(self.fileName): # y, sr = librosa.load(self.fileName) # f0, voiced_flag, voiced_probs = librosa.pyin(y, fmin=librosa.note_to_hz('C4'), # fmax=librosa.note_to_hz('C5')) # f0 = f0[~np.isnan(f0)] # times = librosa.times_like(f0) # level = optimize.curve_fit(lambda x, b: b, times, np.nan_to_num(f0))[0] # pitch = np.around(level[0], decimals=3).astype(float) pitch = self.pitch_estimation(self.fileName) self.ui.c1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.c1m.setText(str(pitch)) F0 = 261.626 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.c1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.c1e.setText(str(err)) elif name == 'd1': pitch = self.pitch_estimation(self.fileName) self.ui.d1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.d1m.setText(str(pitch)) F0 = 293.665 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.d1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.d1e.setText(str(err)) elif name == 'e1': pitch = self.pitch_estimation(self.fileName) self.ui.e1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.e1m.setText(str(pitch)) F0 = 329.628 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.e1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.e1e.setText(str(err)) elif name == 'f1': pitch = self.pitch_estimation(self.fileName) self.ui.f1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.f1m.setText(str(pitch)) F0 = 349.228 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.f1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.f1e.setText(str(err)) elif name == 'g1': pitch = self.pitch_estimation(self.fileName) self.ui.g1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.g1m.setText(str(pitch)) F0 = 391.995 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.g1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.g1e.setText(str(err)) elif name == 'a1': pitch = self.pitch_estimation(self.fileName) self.ui.a1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.a1m.setText(str(pitch)) F0 = 440.000 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.a1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.a1e.setText(str(err)) elif name == 'b1': pitch = self.pitch_estimation(self.fileName) self.ui.b1m.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.b1m.setText(str(pitch)) F0 = 493.883 err = np.around(np.abs(pitch - F0) / F0, decimals=4) * 100 self.ui.b1e.setStyleSheet("color:rgb(10,10,10,255);" "font-size:32px;" "font-weight:600;" "font-family:Times New Roman;") self.ui.b1e.setText(str(err)) else: QMessageBox.critical(self, "文件名错误", "请输入c1-b1中的音名") ## =============自定义槽函数=============================== def do_stateChanged(self, state): ##状态变化 isRecording = (state == QMediaRecorder.RecordingState) # 正在录制 self.ui.actRecord.setEnabled(not isRecording) # self.ui.actPause.setEnabled(isRecording) self.ui.actStop.setEnabled(isRecording) isStoped = (state == QMediaRecorder.StoppedState) # 已停止 self.ui.btnGetFile.setEnabled(isStoped) self.ui.editOutputFile.setEnabled(isStoped) def do_durationChanged(self, duration): ##持续时间长度变化 self.ui.LabPassTime.setText("已录制 %d 秒" % (duration / 1000)) # ============================================================================ @pyqtSlot() def playaudioc(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/C4.wav"))) self.mediaPlayer.play() @pyqtSlot() def playaudiod(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/D4.wav"))) self.mediaPlayer.play() @pyqtSlot() def playaudioe(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/E4.wav"))) self.mediaPlayer.play() @pyqtSlot() def playaudiof(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/F4.wav"))) self.mediaPlayer.play() @pyqtSlot() def playaudiog(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/G4.wav"))) self.mediaPlayer.play() @pyqtSlot() def playaudioa(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/A4.wav"))) self.mediaPlayer.play() @pyqtSlot() def playaudiob(self): self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile("data/standard/B4.wav"))) self.mediaPlayer.play()
class MainWindow(QMainWindow): def __init__(self): super().__init__() # Controles principales para organizar la ventana. self.setupConstants() self.widget = QWidget(self) # tha main layout self.layout = QVBoxLayout() # the top box with file selections self.input_layout = QHBoxLayout() self.output_layout = QHBoxLayout() self.bottom_layout = QHBoxLayout() self.volume_box = QHBoxLayout() # video playback section self.video_widget = QVideoWidget(self) self.media_player = QMediaPlayer() self.media_player.setVideoOutput(self.video_widget) # initialize audio recording section self.recorder = QAudioRecorder() # labels self.volume_label = QLabel() self.volume_label.setText("Volume") # Buttons for the I/O files selection self.input_file_button = QPushButton("Video Input", self) self.output_file_button = QPushButton("Audio output", self) # path/file line edits self.input_file_edit = QLineEdit() self.output_file_edit = QLineEdit() self.play_button = QPushButton("", self) self.play_button.setIcon(self.play_normal_icon) self.play_button.resize(150, 150) self.stop_button = QPushButton("", self) self.stop_button.setIcon(self.stop_normal_icon) self.record_button = QPushButton("", self) self.record_button.setCheckable(True) self.record_button.setIcon(self.rec_icon) self.seek_slider = QSlider(Qt.Horizontal) self.volume_slider = QSlider(Qt.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(self.media_player.volume()) self.input_layout.addWidget(self.input_file_button) self.input_layout.addWidget(self.input_file_edit) self.output_layout.addWidget(self.output_file_button) self.output_layout.addWidget(self.output_file_edit) self.bottom_layout.addWidget(self.play_button) self.bottom_layout.addWidget(self.stop_button) self.bottom_layout.addWidget(self.record_button) self.bottom_layout.addLayout(self.volume_box) self.volume_box.addWidget(self.volume_label) self.volume_box.addWidget(self.volume_slider) self.layout.addWidget(self.video_widget) self.layout.addLayout(self.bottom_layout) self.layout.addWidget(self.seek_slider) self.layout.addLayout(self.input_layout) self.layout.addLayout(self.output_layout) # Personalizzazione della finestra self.setWindowTitle("Wish' Karaoke! :)") self.resize(800, 600) self.layout.setContentsMargins(10, 10, 10, 10) self.bottom_layout.setContentsMargins(0, 0, 0, 0) self.widget.setLayout(self.layout) self.setCentralWidget(self.widget) self.setupMenus() self.setupUiConnections() def setupMenus(self): # setup the menus self.mainMenu = self.menuBar() # File menu and subitems self.fileMenu = self.mainMenu.addMenu('File') self.exitButton = QAction(self.exit_icon, 'Exit', self) self.exitButton.setShortcut('Ctrl+Q') self.exitButton.setStatusTip('Exit application') self.fileMenu.addAction(self.exitButton) # View menu and related items self.viewMenu = self.mainMenu.addMenu('View') # Fullscreen item self.toggleFullscreenButton = QAction(QIcon(""), 'Fullscreen', self) self.toggleFullscreenButton.setCheckable(True) self.toggleFullscreenButton.setStatusTip('Toggle fullscreen more') self.toggleFullscreenButton.setShortcut("CTRL+SHIFT+F") self.viewMenu.addAction(self.toggleFullscreenButton) # Tools menu and related items self.toolsMenu = self.mainMenu.addMenu('Tools') # Play/Rec bind toggle self.bindPlayRecButton = QAction(QIcon(""), 'Bind Play/Rec', self) self.bindPlayRecButton.setCheckable(True) self.bindPlayRecButton.setStatusTip('Bind Play and Rec') self.toolsMenu.addAction(self.bindPlayRecButton) def setupUiConnections(self): """ Put all the UI connections and event catchers here, just to keep the code clean :return: """ self.record_button.clicked.connect(self.recButtonState) self.seek_slider.sliderMoved.connect(self.media_player.setPosition) self.volume_slider.sliderMoved.connect(self.media_player.setVolume) self.media_player.positionChanged.connect(self.seek_slider.setValue) self.media_player.durationChanged.connect( partial(self.seek_slider.setRange, 0)) self.play_button.clicked.connect(self.play_clicked) self.stop_button.clicked.connect(self.stop_clicked) self.media_player.stateChanged.connect(self.state_changed) # self.input_file_button.clicked.connect(self.selectInputFile) # self.input_file_edit.textChanged.connect(self.setInputMedia) # self.output_file_button.clicked.connect(self.selectOutputFile) self.output_file_edit.textChanged.connect(self.setOutputMedia) # menu connections # fullscreen self.toggleFullscreenButton.toggled.connect(self.toggleFullscreen) # quit self.exitButton.triggered.connect(self.close) # Play/Rec bind self.bindPlayRecButton.toggled.connect(self.bind_play_rec) # Installing event filter for the video widget self.video_widget.installEventFilter(self) def bind_play_rec(self): """ toggle the binding between play and rec to start recording as soon as playback starts. :return: Nothing """ if not self.bindPlayRecStatus: self.bindPlayRecStatus = True else: self.bindPlayRecStatus = False # If binding is active, the REC button is disabled. self.record_button.setDisabled(self.bindPlayRecStatus) def play_clicked(self): """ Start or resume playback. If binding is active, start/pause the audio recording as well """ if (self.media_player.state() in (QMediaPlayer.PausedState, QMediaPlayer.StoppedState)): self.media_player.play() logger.info("(Re)Starting playback") if self.bindPlayRecStatus: if (self.recorder.state() in (QAudioRecorder.PausedState, QAudioRecorder.StoppedState)): logger.info( "Rec/Play bind is on! (Re)Starting Recorder as well.") self.recorder.record() else: self.media_player.pause() logger.info("Pausing playback") if self.bindPlayRecStatus: logger.info("Rec/Play bind is on! Pausing Recorder as well.") self.recorder.pause() def stop_clicked(self): """ Stopping playback. if Play/Rec binding is on, stop also the recorder. """ logger.info("Stopping playback") self.media_player.stop() if self.bindPlayRecStatus: logger.info("Rec/Play bind is on! Stopping Recorder as well.") self.recorder.stop() def state_changed(self, newstate): """ Update buttons. Not really needed, probably. """ states = { QMediaPlayer.PausedState: self.play_normal_icon, QMediaPlayer.PlayingState: self.pause_icon, QMediaPlayer.StoppedState: self.play_normal_icon } self.play_button.setIcon(states[newstate]) # elegant way to enable/disable the stop button self.stop_button.setEnabled(newstate != QMediaPlayer.StoppedState) def eventFilter(self, obj, event): """ Catch MouseButtonDblClick or CTRL+SHIFT+F to toggle fullscreen """ if (event.type() == QEvent.KeyPress and event.modifiers() & Qt.ShiftModifier \ and event.modifiers() & Qt.ControlModifier and event.key() == 70) \ or event.type() == QEvent.MouseButtonDblClick: obj.setFullScreen(not obj.isFullScreen()) return False def toggleFullscreen(self): self.video_widget.setFullScreen(not self.video_widget.isFullScreen()) def selectInputFile(self): """ Just a small function to open a file dialog """ #self.input_file_edit.setText(QFileDialog.getOpenFileName()) # encode the resulting filename as UNICODE text self.input_filename, _ = QFileDialog.getOpenFileName() self.input_file_edit.setText(self.input_filename) def setInputMedia(self, filename): self.media_player.setMedia(QMediaContent(QUrl.fromLocalFile(filename))) def selectOutputFile(self): """ Just a small function to open a file dialog """ self.output_filename, _ = QFileDialog.getSaveFileName() self.output_file_edit.setText(self.output_filename) def setOutputMedia(self, filename): self.recorder.setOutputLocation(QUrl.fromLocalFile(filename)) def recButtonState(self): if self.record_button.isChecked(): self.doRecord() else: self.stopRecord() def doRecord(self): """ TODO: define this function better, toggled by the Rec button :return: """ print("Recording") self.recorder.record() def stopRecord(self): print("Stopping recorder") self.recorder.stop() def setupConstants(self): self.rec_icon = QIcon.fromTheme("media-record", QIcon("icons/rec.png")) self.play_normal_icon = QIcon.fromTheme("media-playback-start", QIcon("icons/Play-Normal.png")) self.stop_normal_icon = QIcon.fromTheme("media-playback-stop", QIcon("icons/Stop-Normal.png")) self.exit_icon = QIcon.fromTheme("application-exit", QIcon("icons/application-exit.png")) self.pause_icon = QIcon.fromTheme( "media-playback-pause", QIcon("icons/Pause-Disabled-icon.png")) self.bindPlayRecStatus = False
class Recorder(QtWidgets.QWidget): def __init__(self, parent=None): super().__init__(parent) self.ui = Ui_Form() self.ui.setupUi(self) self.recorder = QAudioRecorder(self) self.recorder.stateChanged.connect(self.do_stateChanged) self.recorder.durationChanged.connect(self.do_durationChanged) self.probe = QAudioProbe(self) self.probe.setSource(self.recorder) self.probe.audioBufferProbed.connect(self.do_processBuffer) if self.recorder.defaultAudioInput() == "": # str类型 return # 无音频录入设备 for device in self.recorder.audioInputs(): self.ui.comboDevices.addItem(device) # 音频录入设备列表 for codecName in self.recorder.supportedAudioCodecs(): self.ui.comboCodec.addItem(codecName) # 支持的音频编码 sampleList, isContinuous = self.recorder.supportedAudioSampleRates() # isContinuous 是否支持连续的采样率,与C++不一样 for i in range(len(sampleList)): self.ui.comboSampleRate.addItem("%d" % sampleList[i]) # 支持的采样率 # channels self.ui.comboChannels.addItem("1") self.ui.comboChannels.addItem("2") self.ui.comboChannels.addItem("4") # quality self.ui.sliderQuality.setRange(0, QMultimedia.VeryHighQuality) self.ui.sliderQuality.setValue(QMultimedia.NormalQuality) # bitrates self.ui.comboBitrate.addItem("32000") self.ui.comboBitrate.addItem("64000") self.ui.comboBitrate.addItem("96000") self.ui.comboBitrate.addItem("128000") self.__addToolbar() def __addToolbar(self): layout = QtWidgets.QVBoxLayout() Astart = QtWidgets.QToolButton() Astart.setText("开始") Astart.setDefaultAction(self.ui.actRecordStart) Apause = QtWidgets.QToolButton() Apause.setText("暂停") Apause.setDefaultAction(self.ui.actRecordPause) Astop = QtWidgets.QToolButton() Astop.setText("停止") Astop.setDefaultAction(self.ui.actRecordStop) self.toolbar = QtWidgets.QToolBar() self.toolbar.addWidget(Astart) self.toolbar.addWidget(Apause) self.toolbar.addWidget(Astop) layout.addWidget(self.toolbar) layout.addWidget(self.ui.widget) self.setLayout(layout) def __setRecordParams(self): selectedFile=self.ui.editOutputFile.text().strip() if (selectedFile ==""): QMessageBox.critical(self,"错误","请先设置录音输出文件") return False if os.path.exists(selectedFile): os.remove(selectedFile)#删除已有文件 recordFile=QUrl.fromLocalFile(selectedFile) self.recorder.setOutputLocation(recordFile) #设置输出文件 recordDevice=self.ui.comboDevices.currentText() self.recorder.setAudioInput(recordDevice) #设置录入设备 settings=QAudioEncoderSettings() #音频编码设置 settings.setCodec(self.ui.comboCodec.currentText()) #编码 sampRate=int(self.ui.comboSampleRate.currentText()) settings.setSampleRate(sampRate) #采样率 bitRate=int(self.ui.comboBitrate.currentText()) settings.setBitRate(bitRate) #比特率 channelCount=int(self.ui.comboChannels.currentText()) settings.setChannelCount(channelCount) #通道数 quality=QMultimedia.EncodingQuality(self.ui.sliderQuality.value()) settings.setQuality(quality) #品质 if self.ui.radioQuality.isChecked(): #编码模式为固定品质,自动决定采样率,采样点大小 settings.setEncodingMode(QMultimedia.ConstantQualityEncoding) else: settings.setEncodingMode(QMultimedia.ConstantBitRateEncoding) #固定比特率 self.recorder.setAudioSettings(settings) #音频设置 return True ## ==========由connectSlotsByName() 自动连接的槽函数================== @pyqtSlot() def on_btnGetFile_clicked(self): ##"录音输出文件"按钮 curPath=os.getcwd() #获取系统当前目录 dlgTitle="选择输出文件" filt="wav文件(*.wav)" fileName,flt,=QFileDialog.getSaveFileName(self, dlgTitle, curPath, filt) if (fileName !=""): self.ui.editOutputFile.setText(fileName) @pyqtSlot() ##开始录音 def on_actRecordStart_triggered(self): success=True if (self.recorder.state() == QMediaRecorder.StoppedState): #已停止,重新设置 success=self.__setRecordParams() #设置录音参数 if success: self.recorder.record() @pyqtSlot() ##暂停 def on_actRecordPause_triggered(self): self.recorder.pause() @pyqtSlot() def on_actRecordStop_triggered(self): self.recorder.stop() ## =============自定义槽函数=============================== def do_stateChanged(self,state): ##状态变化 isRecording=(state==QMediaRecorder.RecordingState) #正在录制 self.ui.actRecordStart.setEnabled(not isRecording) self.ui.actRecordPause.setEnabled(isRecording) self.ui.actRecordStop.setEnabled(isRecording) isStoped=(state==QMediaRecorder.StoppedState) #已停止 self.ui.btnGetFile.setEnabled(isStoped) self.ui.editOutputFile.setEnabled(isStoped) def do_durationChanged(self,duration): ##持续时间长度变化 self.ui.LabPassTime.setText("已录制 %d 秒"%(duration/1000)) def do_processBuffer(self,buffer): ##解析缓冲区数据 self.ui.spin_byteCount.setValue(buffer.byteCount()) #缓冲区字节数 self.ui.spin_duration.setValue(buffer.duration()/1000) #缓冲区时长 self.ui.spin_frameCount.setValue(buffer.frameCount()) #缓冲区帧数 self.ui.spin_sampleCount.setValue(buffer.sampleCount()) #缓冲区采样数 audioFormat=buffer.format() #缓冲区格式,QAudioBuffer self.ui.spin_channelCount.setValue(audioFormat.channelCount()) #通道数 self.ui.spin_sampleSize.setValue(audioFormat.sampleSize()) #采样大小 self.ui.spin_sampleRate.setValue(audioFormat.sampleRate()) #采样率 self.ui.spin_bytesPerFrame.setValue(audioFormat.bytesPerFrame()) #每帧字节数 if (audioFormat.byteOrder()==QAudioFormat.LittleEndian): self.ui.edit_byteOrder.setText("LittleEndian") #字节序 else: self.ui.edit_byteOrder.setText("BigEndian") self.ui.edit_codec.setText(audioFormat.codec()) #编码格式 if (audioFormat.sampleType()==QAudioFormat.SignedInt): #采样点类型 self.ui.edit_sampleType.setText("SignedInt") elif(audioFormat.sampleType()==QAudioFormat.UnSignedInt): self.ui.edit_sampleType.setText("UnSignedInt") elif(audioFormat.sampleType()==QAudioFormat.Float): self.ui.edit_sampleType.setText("Float") else: self.ui.edit_sampleType.setText("Unknown")
class AudioRecorder(QWidget): def __init__(self): super().__init__() self.initializeUI() def initializeUI(self): """Initialize the window and display its contents to the screen.""" self.setFixedSize(360, 540) self.setWindowTitle('9.1 - Audio Recorder') self.audio_path = "" # Empty variable for path to audio file self.setupWindow() self.setupSystemTrayIcon() self.show() def setupWindow(self): """Set up widgets in the main window and the QAudioRecorder instance.""" # Set up two push buttons (the app's first "screen") self.select_path_button = QPushButton("Select Audio Path") self.select_path_button.setObjectName("SelectFile") self.select_path_button.setFixedWidth(140) self.select_path_button.clicked.connect(self.selectAudioPath) self.start_button = QPushButton() self.start_button.setObjectName("StartButton") self.start_button.setEnabled(False) self.start_button.setFixedSize(105, 105) self.start_button.clicked.connect(self.startRecording) # Set up the labels and stop button (the app's second "screen") self.recording_label = QLabel("Recording...") self.recording_label.setFont(QFont("Helvetica [Cronyx]", 32)) self.recording_label.setVisible(False) self.recording_label.setAlignment(Qt.AlignHCenter) self.time_label = QLabel("00:00") self.time_label.setFont(QFont("Helvetica [Cronyx]", 18)) self.time_label.setObjectName("Time") self.time_label.setVisible(False) self.time_label.setAlignment(Qt.AlignHCenter) self.stop_button = QPushButton() self.stop_button.setObjectName("StopButton") self.stop_button.setFixedSize(65, 65) self.stop_button.setVisible(False) self.stop_button.clicked.connect(self.stopRecording) # Set up the main layout self.main_v_box = QVBoxLayout() self.main_v_box.setAlignment(Qt.AlignHCenter) self.main_v_box.addWidget(self.select_path_button) # Force select_path_button to be centered in the window self.main_v_box.setAlignment(self.select_path_button, Qt.AlignCenter) self.main_v_box.addStretch(3) self.main_v_box.addWidget(self.start_button) self.main_v_box.setAlignment(self.start_button, Qt.AlignCenter) self.main_v_box.addWidget(self.recording_label) self.main_v_box.addWidget(self.time_label) self.main_v_box.addStretch(3) self.main_v_box.addWidget(self.stop_button) self.main_v_box.setAlignment(self.stop_button, Qt.AlignCenter) self.main_v_box.addStretch(1) self.setLayout(self.main_v_box) # Set the beginning layout # Specify audio encoder settings audio_settings = QAudioEncoderSettings() # Depending upon your platform or the codecs that you have available, you # will need to change the codec. For Linux users if you are having issues # use "audio/x-vorbis", and then select the .ogg extension when saving # the file audio_settings.setCodec("audio/wav") audio_settings.setQuality(QMultimedia.HighQuality) # Create instance of QAudioRecorder for recording audio self.audio_recorder = QAudioRecorder() # Uncomment to discover possible codecs supported on your platform #print(self.audio_recorder.supportedAudioCodecs()) self.audio_recorder.setEncodingSettings(audio_settings) self.audio_recorder.durationChanged.connect(self.displayTime) def setupSystemTrayIcon(self): """Set up system tray icon and context menu. User can re-open the window if it was closed or quit the application using the tray menu.""" self.tray_icon = QSystemTrayIcon( QIcon(":/resources/images/mic_icon.png")) # Create the actions and context menu for the tray icon tray_menu = QMenu() open_act = tray_menu.addAction("Open") open_act.triggered.connect(self.show) tray_menu.addSeparator() quit_act = tray_menu.addAction("Quit") quit_act.triggered.connect(QApplication.quit) self.tray_icon.setContextMenu(tray_menu) self.tray_icon.show() def selectAudioPath(self): """Open file dialog and choose the directory for saving the audio file.""" path, _ = QFileDialog.getSaveFileName(self, "Save Audio File", os.getenv("HOME"), "WAV (*.wav)") if path: self.audio_path = path self.start_button.setEnabled(True) else: QMessageBox.information(self, "Error", "No directory selected.", QMessageBox.Ok) def startRecording(self): """Set up the audio output file location, reset widget states. Also starts the timer and begins recording. """ self.audio_recorder.setOutputLocation( QUrl.fromLocalFile(self.audio_path)) # Set widget states self.select_path_button.setVisible(False) self.start_button.setVisible(False) self.recording_label.setVisible(True) self.time_label.setVisible(True) self.time_label.setText("00:00") # Update the label self.stop_button.setVisible(True) # Start the timer and begin recording self.audio_recorder.record() def stopRecording(self): """Stop recording, stop the timer, and reset widget states.""" self.audio_recorder.stop() # Reset widget states self.select_path_button.setVisible(True) self.start_button.setVisible(True) self.recording_label.setVisible(False) self.time_label.setVisible(False) self.stop_button.setVisible(False) def displayTime(self, duration): """Calculate the time displayed in the time_label widget.""" minutes, seconds = self.convertTotalTime(duration) time_recorded = "{:02d}:{:02d}".format(minutes, seconds) self.time_label.setText(time_recorded) def convertTotalTime(self, time_in_milli): """Convert time from milliseconds.""" minutes = (time_in_milli / (1000 * 60)) % 60 seconds = (time_in_milli / 1000) % 60 return int(minutes), int(seconds) def closeEvent(self, event): """Display a message in the system tray when the main window has been closed.""" self.tray_icon.showMessage("Notification", "Audio Recorder is still running.", 8000)
class MainWindow_EXEC(): def __init__(self): #-------------------Init QT Setup--------------------------- app = QtWidgets.QApplication(sys.argv) self.MainWindow = QtWidgets.QMainWindow() self.ui = Ui_MainWindow() self.ui.setupUi(self.MainWindow) app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) #------------------Exporting Setup------------------------------ self.ui.export_midi.clicked.connect(self.openDirectory_midi) self.ui.export_midi.setFocusPolicy(QtCore.Qt.NoFocus) self.ui.export_audio.clicked.connect(self.openDirectory_audio) self.ui.export_audio.setFocusPolicy(QtCore.Qt.NoFocus) #------------------Metronome Setup------------------------------ self.ui.metronome_button.clicked.connect(self.metro_thread) #------------------Recording Setup------------------------------ self.ui.start_stop_rec.clicked.connect(self.start_stop_recording) self.ui.play_gui.clicked.connect(self.play) # QAudio setup self.settings = QAudioEncoderSettings() self.settings.setBitRate(16) self.settings.setChannelCount(1) self.audioRecorder = QAudioRecorder() self.audioRecorder.setEncodingSettings(self.settings) self.file_path = os.path.abspath( os.path.join(os.path.dirname(__file__), resource_path("resources/output.wav"))) self.url = QUrl.fromLocalFile(self.file_path) self.audioRecorder.setOutputLocation(self.url) #------------------Audio Terrain Gui Setup------------------------------ self.terrain = Terrain() self.terrain.update() self.terrain_widget = self.terrain.getwidget() self.layout = QtGui.QGridLayout() self.layout.setContentsMargins(0, 0, 0, 0) self.ui.t_widget.setLayout(self.layout) self.layout.addWidget(self.terrain_widget, 0, 0, 1, 1) #------------------Audio Trimmer Setup------------------------------ self.ui.audio_trimmer.clicked.connect(self.trim_audio) if os.path.isfile("resources/output.wav"): self.y, self.sr = librosa.load( resource_path("resources/output.wav"), sr=44100) else: new_wav = AudioSegment.empty() new_wav.export("resources/output.wav", format="wav") self.y, self.sr = librosa.load( resource_path("resources/output.wav"), sr=44100) self.duration = round( librosa.core.get_duration(y=self.y, sr=self.sr) * self.sr) self.maxv = np.iinfo(np.int16).max self.win = pg.GraphicsLayoutWidget() self.p = self.win.addPlot() #removes X & Y Axis and disables mouse movement self.p.showAxis('bottom', show=False) self.p.showAxis('left', show=False) self.p.setMouseEnabled(x=False, y=False) self.region = pg.LinearRegionItem(brush=(100, 100, 100, 60), bounds=(0, self.duration)) self.region.setRegion([0, self.duration]) self.p.addItem(self.region, ignoreBounds=True) self.p.plot(self.y, pen="w") self.layout.addWidget(self.win) self.win.hide() #------------------Midi Setup------------------------------ self.ui.convert_midi.clicked.connect(self.convertMidi) self.ui.midi_play.clicked.connect(self.midiplayer_thread) self.ui.tempo_slider.valueChanged[int].connect(self.tempo_value) self.ui.midi_loop.toggle() # default bpm is 120 self.current_tempo = 120 self.detected_tempo = 120 #------------------Drum Kit Selector Setup---------------------- self.ui.drum_kits.clicked.connect(self.select_drumkit) self.drum_number = 0 self.drum_folders = [ 'Drum_Kit_1', 'Drum_Kit_2', 'Drum_Kit_3', 'Drum_Kit_4' ] self.drum_current = self.drum_folders[self.drum_number] #------------------EXEC Window--------------------------------- self.MainWindow.show() sys.exit(app.exec_()) #--------------------------------------------------------------- #------------------Functions---------------------------------- #------------------Drum Kit Selector------------------------------ def select_drumkit(self): if self.drum_number < 3: self.drum_number += 1 self.drum_current = self.drum_folders[self.drum_number] self.ui.drum_kits.setText(self.drum_current.replace("_", " ")) else: self.drum_number = 0 self.drum_current = self.drum_folders[self.drum_number] self.ui.drum_kits.setText(self.drum_current.replace("_", " ")) #------------------Audio Trimmer------------------------------ def trim_audio(self): # Switch to Trimmer widget self.layout.removeWidget(self.terrain_widget) self.terrain_widget.hide() self.win.show() self.trim_values = self.region.getRegion() self.updateaudio() # Trims signal array with region values self.y = self.y[round(self.trim_values[0]):round(self.trim_values[1])] # save the new signal values to wav librosa.output.write_wav(resource_path("resources/output.wav"), (self.y * self.maxv).astype(np.int16), self.sr) self.updateplot() def updateplot(self): # Replot the trimmed wav and update region bounds self.duration = round( librosa.core.get_duration(y=self.y, sr=self.sr) * self.sr) self.p.plot(clear=True) self.p.plot(self.y, pen="w") self.region = pg.LinearRegionItem(brush=(100, 100, 100, 50), bounds=(0, self.duration)) self.p.addItem(self.region, ignoreBounds=True) self.region.setRegion([0, self.duration]) def updateaudio(self): self.y, self.sr = librosa.load(resource_path("resources/output.wav"), sr=44100) #------------------Metronome Threading------------------------------ def metro_thread(self): if self.ui.metronome_button.isChecked(): print('metronome is On') self.thread = QThread( ) # a new thread to run our background tasks in self.worker = Worker( self.current_tempo) # a new worker to perform those tasks self.worker.moveToThread( self.thread ) # move the worker into the thread, do this first before connecting the signals self.thread.started.connect( self.worker.work ) # begin our worker object's loop when the thread starts running self.thread.start() else: print('metronome is Off') self.stop_loop() self.worker.finished.connect( self.loop_finished ) # do something in the gui when the worker loop ends self.worker.finished.connect( self.thread.quit) # tell the thread it's time to stop running self.worker.finished.connect(self.thread.wait) self.worker.finished.connect( self.worker.deleteLater ) # have worker mark itself for deletion self.thread.finished.connect(self.thread.deleteLater) def stop_loop(self): self.worker.working = False def loop_finished(self): # print('Worker Finished') pass #--------------------------------------------------------- #------------------ MIDI ------------------------------ def tempo_value(self, value): self.current_tempo = value def convertMidi(self): self.ui.convert_midi.setEnabled(False) self.thread2 = QThread() self.worker2 = ConvertMidi_Worker() self.worker2.moveToThread(self.thread2) self.thread2.started.connect(self.worker2.work) self.thread2.start() self.worker2.finished.connect(self.convert_finished) self.worker2.finished.connect(self.thread2.quit) self.worker2.finished.connect(self.thread2.wait) self.worker2.finished.connect(self.worker2.deleteLater) self.thread2.finished.connect(self.thread2.deleteLater) def convert_finished(self, tempo): self.detected_tempo = tempo self.ui.tempo_slider.setValue(self.detected_tempo) self.ui.convert_midi.clearFocus() self.ui.convert_midi.setEnabled(True) print('Midi Conversion finished') def midiplayer_thread(self): if self.ui.midi_play.isChecked() and self.ui.midi_loop.isChecked( ) == False: self.ui.midi_play.setEnabled(False) self.win.hide() self.terrain_widget.show() self.terrain.animate() self.thread3 = QThread() self.worker3 = MidiPlayer_Worker(self.current_tempo, self.drum_current) self.worker3.moveToThread(self.thread3) self.thread3.started.connect(self.worker3.workonce) self.thread3.start() self.worker3.finished2.connect(self.midi_loop_finished2) self.worker3.finished2.connect(self.thread3.quit) self.worker3.finished2.connect(self.thread3.wait) self.worker3.finished2.connect(self.worker3.deleteLater) self.thread3.finished.connect(self.thread3.deleteLater) elif self.ui.midi_play.isChecked() and self.ui.midi_loop.isChecked( ) == True: self.win.hide() self.terrain_widget.show() self.start_Midi_Thread() self.terrain.animate() elif self.ui.midi_play.isChecked() == False: self.terrain.stop_animate() self.stop_Midi_Thread() def start_Midi_Thread(self): self.thread3 = QThread() self.worker3 = MidiPlayer_Worker(self.current_tempo, self.drum_current) self.worker3.moveToThread(self.thread3) self.thread3.started.connect(self.worker3.work) self.thread3.start() def stop_Midi_Thread(self): self.worker3.working = False self.worker3.stop() self.worker3.finished.connect(self.midi_loop_finished) self.worker3.finished.connect(self.thread3.quit) self.worker3.finished.connect(self.thread3.wait) self.worker3.finished.connect(self.worker3.deleteLater) self.thread3.finished.connect(self.thread3.deleteLater) print('done') def midi_loop_finished(self): print('Midi loop Finished') def midi_loop_finished2(self): print('Midi Player Finished') self.ui.midi_play.toggle() self.ui.midi_play.setEnabled(True) self.terrain.stop_animate() #--------------------------------------------------------- #------------------ Recorder & Player ------------------------------ def start_stop_recording(self): if self.ui.start_stop_rec.isChecked(): self.ui.play_gui.setEnabled(False) self.ui.audio_trimmer.setEnabled(False) self.win.hide() self.terrain_widget.show() self.layout.addWidget(self.terrain_widget) self.audioRecorder.record() self.terrain.update() self.terrain.animate() print('Recording...') else: self.ui.play_gui.setEnabled(True) self.ui.audio_trimmer.setEnabled(True) self.terrain.stop_animate() self.audioRecorder.stop() self.layout.removeWidget(self.terrain_widget) self.terrain_widget.hide() self.updateaudio() self.win.show() self.updateplot() print('Stop Recording') def play(self): if self.ui.play_gui.isChecked(): self.win.hide() self.terrain_widget.show() self.player = QSound(resource_path("resources/output.wav")) self.terrain.animate() self.player.play() # if self.player.isFinished(): # self.ui.play_gui.toggle() # print('done') else: self.terrain.stop_animate() self.player.stop() self.player.deleteLater() #------------------ Exporting ------------------------------ def openDirectory_midi(self): self.openDirectoryDialog = QtGui.QFileDialog.getExistingDirectory( self.MainWindow, "Save Midi File") if self.openDirectoryDialog: self.saveMidi(self.openDirectoryDialog) else: pass def openDirectory_audio(self): self.openDirectoryDialog = QtGui.QFileDialog.getExistingDirectory( self.MainWindow, "Save Audio File") if self.openDirectoryDialog: self.saveAudio(self.openDirectoryDialog) else: pass def saveMidi(self, directory): shutil.copy("resources/beatbox.mid", directory) def saveAudio(self, directory): shutil.copy("resources/output.wav", directory)
class App(MainWidget): client = Client('localhost', 5000) client_thread = ClientThread(client) # 后台线程 def __init__(self): super(App, self).__init__() self.loginButton.clicked.connect(self.showLoginDialog) self.client_thread.text_signal.connect(self.showText) # 显示文本消息 self.client_thread.usr_signal.connect(self.showUserList) # 更新在线用户 self.client_thread.file_signal.connect(self.showFile) # 显示文件消息 self.emojis.emoji_signal.connect(self.addEmoji) # 通过QListWidget的当前item变化来切换QStackedWidget中的序号 self.userListWidget.currentRowChanged.connect(self.dialogChanged) self.usrList = [] # 保存上一次的在线用户列表 self.groupList = [] # 群组列表 self.md5 = hashlib.md5() # 用于加密密码 # 录音机 self.recorder = QAudioRecorder(self) settings = QAudioEncoderSettings() settings.setChannelCount(2) settings.setSampleRate(16000) self.recorder.setEncodingSettings(settings) # 处理对话用户改变 def dialogChanged(self, i): # 通过QListWidget的当前item变化来切换QStackedWidget中的序号 self.stackedWidget.setCurrentIndex(i) # enable当前chatWidget的所有按钮 self.stackedWidget.currentWidget().emojiButton.setEnabled(True) self.stackedWidget.currentWidget().sendButton.setEnabled(True) self.stackedWidget.currentWidget().fileButton.setEnabled(True) self.stackedWidget.currentWidget().voiceButton.setEnabled(True) try: # 设置图标 self.userListWidget.currentItem().setIcon( qta.icon('fa.check-circle', color='lightgreen')) except: pass # 显示登陆对话框 def showLoginDialog(self): # 登录对话框 self.d = LoginDialog() self.dialog = QDialog() self.d.setupUi(self.dialog) self.dialog.setWindowFlag(QtCore.Qt.FramelessWindowHint) self.dialog.show() # 点击确认就调用login()进行登录操作 self.d.buttonBox.accepted.connect(self.login) # 登录的相关操作 def login(self): passwd = self.d.lineEdit_2.text() if len(passwd) == 0: print('密码不能为空!') QMessageBox.critical(self, "错误", "密码不能为空!", QMessageBox.Ok) return try: self.client.connect() except: print('连接服务器失败!') QMessageBox.critical(self, "错误", "连接服务器失败!", QMessageBox.Ok) return self.client_thread.start() # 启动线程 self.name = self.d.lineEdit.text() # 用户名 self.client.name = self.name # md5加密密码 self.md5.update(passwd.encode('utf-8')) hashed_passwd = self.md5.hexdigest() data = {'name': self.name, 'passwd': hashed_passwd} self.nameLabel.setText('<u>' + self.name) self.client.add_msg(msgtype.LOGIN, '', data) # 登录信息发给服务器验证 self.stackedWidget.removeWidget( self.stackedWidget.findChild(ChatWidget, name='unlogin')) # 移除未登录显示的界面 # 关闭登录按钮 self.loginButton.close() # 为用户建立文件夹 if not os.path.exists('./temp/%s' % self.client.name): os.makedirs('./temp/%s' % self.client.name) # 发送文字消息 def sendText(self): text = self.stackedWidget.currentWidget().msgEdit.toPlainText() if not len(text): return # 长度为零,不发送 try: to = self.userListWidget.selectedItems()[0].text() except: # 出现异常是因为没有选择对话用户 return if to != self.name: data = {'to': to, 'text': text} self.client.add_msg(msgtype.TEXT, '', data) self.stackedWidget.currentWidget().msgEdit.clear() # 清空输入框 # 显示在聊天记录中 item = QListWidgetItem() item.setText(text) item.setBackground(QColor('#F19483')) item.setTextAlignment(Qt.AlignRight) self.stackedWidget.currentWidget().msgList.addItem(item) # 显示收到的文字消息 def showText(self, msg): from_user = msg['from'] text = msg['text'] item = QListWidgetItem() if from_user in self.usrList: # 一对一 item.setText(text) elif from_user in self.groupList: # 群聊 user = msg['user'] # 发言用户 if user == self.name: return item.setText('<%s>' % user + text) item.setTextAlignment(Qt.AlignLeft) item.setBackground(QColor('#F7D420')) chat = self.stackedWidget.findChild( ChatWidget, name=from_user) # 找到对应的chatWidget插入新的消息 chat.msgList.addItem(item) chat.msgList.setCurrentRow(chat.msgList.count() - 1) if from_user in self.usrList: if self.stackedWidget.currentIndex( ) != self.usrList.index(from_user) + len(self.groupList): # 如果当前没有和该用户对话,就显示未读图标,并弹出通知 user = self.userListWidget.item( self.usrList.index(from_user) + len(self.groupList)) user.setIcon(qta.icon('fa.circle', color='orange')) self.tray.showMessage( from_user, text, qta.icon('fa5.comment-alt', color='white')) elif from_user in self.groupList: if self.stackedWidget.currentIndex() != self.groupList.index( from_user): # 如果当前没有看这个群聊,就显示未读图标,并弹出通知 user = self.userListWidget.item( self.groupList.index(from_user)) user.setIcon(qta.icon('fa.circle', color='orange')) self.tray.showMessage( from_user, text, qta.icon('fa5.comment-alt', color='white')) # 更新在线用户列表和对应的chatWidget def showUserList(self, usr_list): users = usr_list['users'] groups = usr_list['groups'] # 群组 for i in groups: if i not in self.groupList: # 添加右侧的chatWidget chatWidget = ChatWidget() chatWidget.setObjectName(i) chatWidget.emojiButton.clicked.connect(self.emojis.show) chatWidget.sendButton.clicked.connect(self.sendText) chatWidget.fileButton.clicked.connect(self.sendFile) chatWidget.voiceButton.pressed.connect(self.startRecord) chatWidget.voiceButton.released.connect(self.stopRecord) self.stackedWidget.addWidget(chatWidget) # 添加到左侧的用户列表 item = QListWidgetItem(i) item.setSizeHint(QSize(16777215, 60)) item.setTextAlignment(Qt.AlignCenter) item.setText(i) item.setIcon(qta.icon('fa.check-circle', color='lightgreen')) self.userListWidget.addItem(item) # 清空用户列表 for i in range(len(groups), self.userListWidget.count()): self.userListWidget.takeItem(len(groups)) # 用户 for i in users: # 添加到用户列表 item = QListWidgetItem(i) item.setSizeHint(QSize(16777215, 60)) item.setTextAlignment(Qt.AlignCenter) item.setText(i) item.setIcon(qta.icon('fa.check-circle', color='lightgreen')) self.userListWidget.addItem(item) # 对于下线的用户,移除对应的chatWidget for i in self.stackedWidget.findChildren(ChatWidget): if i.objectName() not in users and i.objectName() not in groups: self.stackedWidget.removeWidget(i) for i in users: if i not in self.usrList: # 新用户 # 添加右侧的chatWidget chatWidget = ChatWidget() chatWidget.setObjectName(i) chatWidget.emojiButton.clicked.connect(self.emojis.show) chatWidget.sendButton.clicked.connect(self.sendText) chatWidget.fileButton.clicked.connect(self.sendFile) chatWidget.voiceButton.pressed.connect(self.startRecord) chatWidget.voiceButton.released.connect(self.stopRecord) self.stackedWidget.addWidget(chatWidget) self.usrList = users # 更新用户列表 self.groupList = groups # 更新群组列表 # 发送文件 def sendFile(self): fileName, filetype = QFileDialog.getOpenFileName(self, "选取文件", "./") if len(fileName) == 0: return to = self.userListWidget.selectedItems()[0].text() # 发送给的用户 if to != self.name: self.client.add_msg(msgtype.FILE, '', { 'to': to, 'filename': fileName }) # 文件显示在聊天记录中 item = QListWidgetItem() item.setSizeHint(QSize(200, 80)) fileWidget = FileWidget(fileName, 0) item.setBackground(QColor('#F19483')) fileWidget.fileButton.setObjectName(fileName) fileWidget.fileButton.clicked.connect(self.openFile) self.stackedWidget.currentWidget().msgList.addItem(item) self.stackedWidget.currentWidget().msgList.setItemWidget( item, fileWidget) # 显示收到的文件 def showFile(self, msg): from_user = msg['from'] filename = msg['filename'] chat = self.stackedWidget.findChild( ChatWidget, name=from_user) # 找到对应的chatWidget插入新的消息 if from_user in self.usrList: # 一对一 if self.stackedWidget.currentIndex( ) != self.usrList.index(from_user) + len(self.groupList): # 如果当前没有和该用户对话,就显示未读图标 user = self.userListWidget.item( self.usrList.index(from_user) + len(self.groupList)) user.setIcon(qta.icon('fa.circle', color='orange')) self.tray.showMessage(from_user, filename, qta.icon('fa5.file', color='white')) elif from_user in self.groupList: # 群聊 item = QListWidgetItem() item.setText('<%s>' % msg['user']) item.setBackground(QColor('#F7D420')) item.setSizeHint(QSize(200, 40)) chat.msgList.addItem(item) if self.stackedWidget.currentIndex() != self.groupList.index( from_user): user = self.userListWidget.item( self.groupList.index(from_user)) user.setIcon(qta.icon('fa.circle', color='orange')) self.tray.showMessage(from_user, filename, qta.icon('fa5.file', color='white')) # 文件显示在聊天记录中 item = QListWidgetItem() item.setBackground(QColor('#F7D420')) item.setSizeHint(QSize(200, 80)) fileWidget = FileWidget(filename, 1) fileWidget.fileButton.setObjectName('./temp/%s/' % self.name + filename) if filename.endswith('.wav'): # 语音消息 fileWidget.fileButton.clicked.connect(self.playVoice) else: fileWidget.fileButton.clicked.connect(self.openFile) chat.msgList.addItem(item) chat.msgList.setItemWidget(item, fileWidget) # 打开点击的文件 def openFile(self): filename = self.sender().objectName() QDesktopServices.openUrl(QUrl.fromLocalFile(filename)) # 开始录音 def startRecord(self): print('开始录音...') self.stackedWidget.currentWidget().voiceButton.setIcon( qta.icon('fa.spinner')) # 设置图标 # 设置文件名 self.voiceFileName = os.path.abspath('.') + '\\temp\\%s\\%s.wav' % ( self.name, time.strftime('%Y%m%d%H%M%S', time.localtime())) print(self.voiceFileName) self.recorder.setOutputLocation(QUrl.fromLocalFile(self.voiceFileName)) self.recorder.record() # 开始录音 # 停止录音并发送 def stopRecord(self): self.stackedWidget.currentWidget().voiceButton.setIcon( qta.icon('fa.microphone')) self.recorder.stop() print('录音结束') to = self.userListWidget.selectedItems()[0].text() if to != self.name: self.client.add_msg(msgtype.FILE, '', { 'to': to, 'filename': self.voiceFileName }) item = QListWidgetItem() item.setSizeHint(QSize(200, 80)) item.setBackground(QColor('#F19483')) fileWidget = FileWidget(self.voiceFileName, 0) fileWidget.fileButton.setObjectName(self.voiceFileName) fileWidget.fileButton.clicked.connect(self.playVoice) self.stackedWidget.currentWidget().msgList.addItem(item) self.stackedWidget.currentWidget().msgList.setItemWidget( item, fileWidget) # 播放语音 def playVoice(self): print('playing ' + self.sender().objectName()) sound = QtMultimedia.QSound(self.sender().objectName()) # 取得文件名 sound.play() loop = QEventLoop() loop.exec() # 向输入框中添加选中的表情 def addEmoji(self, emoji): self.stackedWidget.currentWidget().msgEdit.setText( self.stackedWidget.currentWidget().msgEdit.toPlainText() + emoji)