mirror of
https://github.com/aidygus/LinVAM.git
synced 2024-11-26 10:38:06 +11:00
Compare commits
No commits in common. "a229683c5748ffd3b4fd43c1ba25c0387eb9f69d" and "60b143be889448a9fad6e390308f7279d0427644" have entirely different histories.
a229683c57
...
60b143be88
3
.gitignore
vendored
3
.gitignore
vendored
@ -127,8 +127,5 @@ dmypy.json
|
|||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# Ignore voicepack files
|
|
||||||
voicepacks/*
|
|
||||||
|
|
||||||
# End of https://www.gitignore.io/api/python
|
# End of https://www.gitignore.io/api/python
|
||||||
|
|
||||||
|
33
README.md
33
README.md
@ -1,6 +1,5 @@
|
|||||||
# LinVAM
|
# LinVAM
|
||||||
Linux Voice Activated Macro
|
Linux Voice Activated Macro
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
This project is currently a work-in-progress and is minimally functional only for english.
|
This project is currently a work-in-progress and is minimally functional only for english.
|
||||||
|
|
||||||
@ -11,7 +10,6 @@ Known bugs and planned additions
|
|||||||
- Remember last loaded profile and load on start
|
- Remember last loaded profile and load on start
|
||||||
- Log window showing spoken words the V2T recognises with ability to right click and assign voice command and actions to current profile
|
- Log window showing spoken words the V2T recognises with ability to right click and assign voice command and actions to current profile
|
||||||
- Support for joysticks and gaming devices
|
- Support for joysticks and gaming devices
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- python3
|
- python3
|
||||||
- PyQt5
|
- PyQt5
|
||||||
@ -19,33 +17,20 @@ Known bugs and planned additions
|
|||||||
- pyaudio
|
- pyaudio
|
||||||
- pocketsphinx
|
- pocketsphinx
|
||||||
- swig3.0
|
- swig3.0
|
||||||
|
|
||||||
## Optional requirements
|
|
||||||
- xdotool
|
|
||||||
- ffplay (part of ffmpeg, usually already installed)
|
|
||||||
- HCS voicepacks
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
- $ pip3 install PyQt5
|
- $ pip3 install PyQt5
|
||||||
- $ pip3 install python3-xlib
|
- $ pip3 install python3-xlib
|
||||||
- $ pip3 install pyaudio
|
- $ pip3 install pyaudio
|
||||||
- $ pip3 install pocketsphinx
|
- $ pip3 install pocketsphinx
|
||||||
- $ sudo apt-get install swig3.0
|
- $ sudo apt-get install swig3.0
|
||||||
- $ (optional) sudo apt install xdotool
|
|
||||||
- $ (optional) sudo apt install ffmpeg
|
|
||||||
- $ sudo ln -s /usr/bin/swig3.0 /usr/bin/swig
|
- $ sudo ln -s /usr/bin/swig3.0 /usr/bin/swig
|
||||||
- $ git clone https://github.com/aidygus/LinVAM.git
|
- $ git clone https://github.com/aidygus/LinVAM.git
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
This script must be run with root privilege because it must hook and simulate input devices such as keyboard, mouse etc.
|
This script must be run with root privilege because it must hook and simulate input devices such as keyboard, mouse etc.
|
||||||
- $ cd LinVAM
|
- $ cd LinVAM
|
||||||
- $ xhost +
|
- $ xhost +
|
||||||
- $ sudo ./main.py
|
- $ sudo ./main.py
|
||||||
|
|
||||||
As an alternative, if you use the X window manager and have xdotool installed, you can run the script like this:
|
|
||||||
- $ cd LinVAM
|
|
||||||
- $ ./main.py -noroot
|
|
||||||
|
|
||||||
### Profiles
|
### Profiles
|
||||||
Multiple profiles are supported. To create a new profile for a specific task/game click new and the main profile editor window will be displayed
|
Multiple profiles are supported. To create a new profile for a specific task/game click new and the main profile editor window will be displayed
|
||||||
|
|
||||||
@ -61,21 +46,3 @@ You can also assign mouse movements and system commands if you require (eg openi
|
|||||||
![Main GUI](https://raw.githubusercontent.com/aidygus/LinVAM/master/.img/complex.png)
|
![Main GUI](https://raw.githubusercontent.com/aidygus/LinVAM/master/.img/complex.png)
|
||||||
### Threshold
|
### Threshold
|
||||||
As a rough guide use a value of 10 for each syllable of a word then tweak it down for better accuracy.
|
As a rough guide use a value of 10 for each syllable of a word then tweak it down for better accuracy.
|
||||||
|
|
||||||
### Output audio
|
|
||||||
In the Command Edit Dialog, chose 'Other' and then 'Play sound'. Pick the sound you would like to play.
|
|
||||||
For this to work you need to copy any audio file you would like to use to the folder 'voicepacks'.
|
|
||||||
You are required to create a subfolder to hold all your audio files (voicepack folder), then within that subfolder, create as many folders as you like to group your audio files (category folders).
|
|
||||||
Place the audio file into these category folders or in any subfolder within a category folder.
|
|
||||||
In theory any audio file should work, but tested only with MP3 files.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
/voicepacks/my voicepack/custom commands/hello.mp3
|
|
||||||
/voicepacks/my voicepack/other/thank you.mp3
|
|
||||||
|
|
||||||
If you own a HCS voicepack, copy the whole voicepack folder (like 'hcspack', 'hcspack-eden', ...) to the 'voicepacks' folder, so it reads like this:
|
|
||||||
/voicepacks/hcspack/...
|
|
||||||
|
|
||||||
### Improve voice recognition accuracy
|
|
||||||
Please see this resource on how to train the acoustic model of pocketsphinx to match your voice:
|
|
||||||
https://cmusphinx.github.io/wiki/tutorialadapt/
|
|
66
changes.md
66
changes.md
@ -1,66 +0,0 @@
|
|||||||
# Changelog smirgol
|
|
||||||
|
|
||||||
### Issues:
|
|
||||||
- (fixed) voice recognition autostarts, there is no way to disable it. the flag "self.m_listening" does nothing
|
|
||||||
- (fixed) when profile is changed, the new commands will not be loaded, as the voice recognition is already running - by default with the Elite Dangerous settings. You cannot use any other profile.
|
|
||||||
- (fixed) "When I say" commands need to be all lower case, otherwise it won't be recognized.
|
|
||||||
|
|
||||||
### Known/New Issues:
|
|
||||||
- When using external speakers it will pick up a playing audio sample and might trigger commands. Use headphones! :)
|
|
||||||
- On a Valve Index headset, the earphones, while being good, will feed the Index microphone causing troubles with the voice recognition.
|
|
||||||
There's probably nothing that can be done about it, maybe some sort of audio filter could be introduced to cut off low-volume sounds?
|
|
||||||
Lower voice audio for now to minimize the issue.
|
|
||||||
- There sometimes is a window error in the console when clicking on a 'cancel' button, does not seem to do harm, but should fix that if possible.
|
|
||||||
- Sometimes keypress signals are not recognized by game, this is the case with xdotool, don't know if this also happens with standard keyboard library.
|
|
||||||
It seems to solve the issue if you at least once manually press any key while the game window is focused
|
|
||||||
|
|
||||||
### Added:
|
|
||||||
- hacked profileexcutor to reload command list when changing it. it did not do that,
|
|
||||||
so changing the profile had no effect at all.
|
|
||||||
- hacked profileexecutor to somewhat make use of "Enable listening".
|
|
||||||
It was an option without functionality at all until now.
|
|
||||||
- created new directory 'voicepacks', copy HCS voicepacks here
|
|
||||||
- new command to play a sound
|
|
||||||
- new gui to select sounds from voicepacks
|
|
||||||
- created playsound class that reads voicepack files and plays audio files with ffplay
|
|
||||||
- added alternate keypress handling using xdotool, which won't require root privileges.
|
|
||||||
might not work for any key existing in profiles, as some need to be remapped. added some remappings to profileexecutor.py pressKey()
|
|
||||||
- added basic command line argument reading to set some configs. right now there:
|
|
||||||
-noroot - will enable xdotool usage for keypresses
|
|
||||||
-xdowindowid <windowid> - will send keypresses to this window only, makes it more relyable when window is not focused for any reason
|
|
||||||
- added auto-detection of Elite Dangerous client window id if -noroot is used and no -xdowindowid is supplied.
|
|
||||||
for this to work, start this script AFTER the client is already running.
|
|
||||||
- monkey-wrenched a volume slider to the main window
|
|
||||||
- added confirmation dialog for removing a profile (!)
|
|
||||||
- copy existing profile. added a copy button to the main menu
|
|
||||||
- properly shutdown audio recording stream
|
|
||||||
- updated gitignore to ignore audio files
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
- ffplay
|
|
||||||
- xdotool (optional, won't need root privileges for keypresses)
|
|
||||||
|
|
||||||
Not sure about these, I had them installed already:
|
|
||||||
- import subprocess
|
|
||||||
- import shlex
|
|
||||||
- import signal
|
|
||||||
|
|
||||||
### TODO
|
|
||||||
- select multiple files (from same category) to play one of them randomly
|
|
||||||
|
|
||||||
### FUTURE IDEAS
|
|
||||||
- remember settings and load on start (nice to have)
|
|
||||||
- change key assignment logic to record a keypress / buttonpress combo
|
|
||||||
- will definitely break existing profiles, as we need to record keycodes and stuff
|
|
||||||
- maybe add a state machine like voice attack does
|
|
||||||
- look into reading log files from elite dangerous to trigger certain actions.
|
|
||||||
this would be a very game specific addition though...
|
|
||||||
- make connection to a specific voice pack more loose, so you can easily change the voice pack,
|
|
||||||
without re-editing all the commands. maybe even a different voice pack for certain actions,
|
|
||||||
like in voice attack (crewmen)
|
|
||||||
- reorganize main window to hold more settings and a log window for displaying actions
|
|
||||||
- look into other text2speech engines
|
|
||||||
i gave julius-speech a quick try. its pretty easy to setup and looks promising, but it
|
|
||||||
also has its problems with detecting some "exotic" words. as not being a native
|
|
||||||
english speaker, my pronounciation is not the best sometimes, which doesn't help. :)
|
|
||||||
- look into make recognized words using regex expressions
|
|
@ -5,7 +5,6 @@ from ui_commandeditwnd import Ui_CommandEditDialog
|
|||||||
from keyactioneditwnd import KeyActionEditWnd
|
from keyactioneditwnd import KeyActionEditWnd
|
||||||
from mouseactioneditwnd import MouseActionEditWnd
|
from mouseactioneditwnd import MouseActionEditWnd
|
||||||
from pauseactioneditwnd import PauseActionEditWnd
|
from pauseactioneditwnd import PauseActionEditWnd
|
||||||
from soundactioneditwnd import SoundActionEditWnd
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class CommandEditWnd(QDialog):
|
class CommandEditWnd(QDialog):
|
||||||
@ -13,7 +12,6 @@ class CommandEditWnd(QDialog):
|
|||||||
super().__init__(p_parent)
|
super().__init__(p_parent)
|
||||||
self.ui = Ui_CommandEditDialog()
|
self.ui = Ui_CommandEditDialog()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.m_parent = p_parent
|
|
||||||
|
|
||||||
self.ui.deleteBut.clicked.connect(self.slotDelete)
|
self.ui.deleteBut.clicked.connect(self.slotDelete)
|
||||||
self.ui.ok.clicked.connect(self.slotOK)
|
self.ui.ok.clicked.connect(self.slotOK)
|
||||||
@ -29,7 +27,6 @@ class CommandEditWnd(QDialog):
|
|||||||
w_otherMenu = QMenu()
|
w_otherMenu = QMenu()
|
||||||
w_otherMenu.addAction('Stop Another Command', self.slotStopAnotherCommand)
|
w_otherMenu.addAction('Stop Another Command', self.slotStopAnotherCommand)
|
||||||
w_otherMenu.addAction('Execute Another Command', self.slotDoAnotherCommand)
|
w_otherMenu.addAction('Execute Another Command', self.slotDoAnotherCommand)
|
||||||
w_otherMenu.addAction('Play Sound', self.slotNewSoundEdit)
|
|
||||||
self.ui.otherBut.setMenu(w_otherMenu)
|
self.ui.otherBut.setMenu(w_otherMenu)
|
||||||
|
|
||||||
self.m_command = {}
|
self.m_command = {}
|
||||||
@ -76,14 +73,6 @@ class CommandEditWnd(QDialog):
|
|||||||
w_commandDoAction['command name'] = text
|
w_commandDoAction['command name'] = text
|
||||||
self.addAction(w_commandDoAction)
|
self.addAction(w_commandDoAction)
|
||||||
|
|
||||||
def slotDoPlaySound(self):
|
|
||||||
text, okPressed = QInputDialog.getItem(self, "Set sound to play", "Enter sound file:", list(self.m_parent.m_parent.m_sound.m_sounds), 0, False)
|
|
||||||
if okPressed and text != '':
|
|
||||||
w_commandDoAction = {}
|
|
||||||
w_commandDoAction['name'] = 'command play sound'
|
|
||||||
w_commandDoAction['command name'] = text
|
|
||||||
self.addAction(w_commandDoAction)
|
|
||||||
|
|
||||||
def slotNewKeyEdit(self):
|
def slotNewKeyEdit(self):
|
||||||
w_keyEditWnd = KeyActionEditWnd(None, self)
|
w_keyEditWnd = KeyActionEditWnd(None, self)
|
||||||
if w_keyEditWnd.exec() == QDialog.Accepted:
|
if w_keyEditWnd.exec() == QDialog.Accepted:
|
||||||
@ -99,11 +88,6 @@ class CommandEditWnd(QDialog):
|
|||||||
if w_pauseEditWnd.exec() == QDialog.Accepted:
|
if w_pauseEditWnd.exec() == QDialog.Accepted:
|
||||||
self.addAction(w_pauseEditWnd.m_pauseAction)
|
self.addAction(w_pauseEditWnd.m_pauseAction)
|
||||||
|
|
||||||
def slotNewSoundEdit(self):
|
|
||||||
w_soundEditWnd = SoundActionEditWnd(self.m_parent.m_parent.m_sound, None, self)
|
|
||||||
if w_soundEditWnd.exec() == QDialog.Accepted:
|
|
||||||
self.addAction(w_soundEditWnd.m_soundAction)
|
|
||||||
|
|
||||||
def slotActionUp(self):
|
def slotActionUp(self):
|
||||||
currentIndex = self.ui.actionsListWidget.currentRow()
|
currentIndex = self.ui.actionsListWidget.currentRow()
|
||||||
currentItem = self.ui.actionsListWidget.takeItem(currentIndex);
|
currentItem = self.ui.actionsListWidget.takeItem(currentIndex);
|
||||||
@ -148,10 +132,6 @@ class CommandEditWnd(QDialog):
|
|||||||
if okPressed and text != '':
|
if okPressed and text != '':
|
||||||
w_action['command name'] = text
|
w_action['command name'] = text
|
||||||
w_jsonAction = json.dumps(w_action)
|
w_jsonAction = json.dumps(w_action)
|
||||||
elif w_action['name'] == 'play sound':
|
|
||||||
w_soundEditWnd = SoundActionEditWnd(self.m_parent.m_parent.m_sound, w_action, self)
|
|
||||||
if w_soundEditWnd.exec() == QDialog.Accepted:
|
|
||||||
w_jsonAction = json.dumps(w_soundEditWnd.m_soundAction)
|
|
||||||
|
|
||||||
w_item.setText(w_jsonAction)
|
w_item.setText(w_jsonAction)
|
||||||
w_item.setData(Qt.UserRole, w_jsonAction)
|
w_item.setData(Qt.UserRole, w_jsonAction)
|
||||||
|
518
main.py
518
main.py
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/env python3
|
||||||
from PyQt5.QtCore import *
|
from PyQt5.QtCore import *
|
||||||
from PyQt5.QtGui import *
|
from PyQt5.QtGui import *
|
||||||
from PyQt5.QtWidgets import *
|
from PyQt5.QtWidgets import *
|
||||||
@ -11,324 +11,252 @@ import pickle
|
|||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import shlex
|
|
||||||
from soundfiles import SoundFiles
|
|
||||||
|
|
||||||
class MainWnd(QWidget):
|
class MainWnd(QWidget):
|
||||||
def __init__(self, p_parent = None):
|
def __init__(self, p_parent = None):
|
||||||
super().__init__(p_parent)
|
super().__init__(p_parent)
|
||||||
|
self.ui = Ui_MainWidget()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self.m_profileExecutor = ProfileExecutor()
|
||||||
|
|
||||||
self.ui = Ui_MainWidget()
|
self.ui.profileCbx.currentIndexChanged.connect(self.slotProfileChanged)
|
||||||
self.ui.setupUi(self)
|
self.ui.addBut.clicked.connect(self.slotAddNewProfile)
|
||||||
self.handleArgs()
|
self.ui.editBut.clicked.connect(self.slotEditProfile)
|
||||||
self.m_sound = SoundFiles()
|
self.ui.removeBut.clicked.connect(self.slotRemoveProfile)
|
||||||
self.m_profileExecutor = ProfileExecutor(None, self)
|
self.ui.listeningChk.stateChanged.connect(self.slotListeningEnabled)
|
||||||
|
self.ui.ok.clicked.connect(self.slotOK)
|
||||||
|
self.ui.cancel.clicked.connect(self.slotCancel)
|
||||||
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
if not os.geteuid() == 0 and not self.m_config['noroot'] == 1:
|
if self.loadFromDatabase() > 0 :
|
||||||
print("\033[93m\nWARNING: no root privileges, unable to send key strokes to the system.\nConsider running this as root.\nFor that you might need to install some python modules for the root user. \n\033[0m")
|
# if self.loadTestProfiles() > 0:
|
||||||
|
w_jsonProfile = self.ui.profileCbx.itemData(0)
|
||||||
|
if w_jsonProfile != None:
|
||||||
|
self.m_activeProfile = json.loads(w_jsonProfile)
|
||||||
|
self.m_profileExecutor.setProfile(self.m_activeProfile)
|
||||||
|
self.m_profileExecutor.start()
|
||||||
|
|
||||||
self.ui.profileCbx.currentIndexChanged.connect(self.slotProfileChanged)
|
def saveToDatabase(self):
|
||||||
self.ui.addBut.clicked.connect(self.slotAddNewProfile)
|
w_profiles = []
|
||||||
self.ui.editBut.clicked.connect(self.slotEditProfile)
|
w_profileCnt = self.ui.profileCbx.count()
|
||||||
self.ui.copyBut.clicked.connect(self.slotCopyProfile)
|
for w_idx in range(w_profileCnt):
|
||||||
self.ui.removeBut.clicked.connect(self.slotRemoveProfile)
|
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
|
||||||
self.ui.listeningChk.stateChanged.connect(self.slotListeningEnabled)
|
if w_jsonProfile == None:
|
||||||
self.ui.ok.clicked.connect(self.slotOK)
|
continue
|
||||||
self.ui.cancel.clicked.connect(self.slotCancel)
|
|
||||||
self.ui.sliderVolume.valueChanged.connect(lambda: self.m_sound.setVolume(self.ui.sliderVolume.value()))
|
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
||||||
|
|
||||||
if self.loadFromDatabase() > 0 :
|
w_profile = json.loads(w_jsonProfile)
|
||||||
# if self.loadTestProfiles() > 0:
|
w_profiles.append(w_profile)
|
||||||
w_jsonProfile = self.ui.profileCbx.itemData(0)
|
|
||||||
if w_jsonProfile != None:
|
|
||||||
self.m_activeProfile = json.loads(w_jsonProfile)
|
|
||||||
self.m_profileExecutor.setProfile(self.m_activeProfile)
|
|
||||||
#self.m_profileExecutor.start()
|
|
||||||
|
|
||||||
|
with open(self.getSettingsPath("profiles.dat"), "wb") as f:
|
||||||
|
pickle.dump(w_profiles, f, pickle.HIGHEST_PROTOCOL)
|
||||||
|
|
||||||
def saveToDatabase(self):
|
def loadFromDatabase(self):
|
||||||
w_profiles = []
|
w_profiles = []
|
||||||
w_profileCnt = self.ui.profileCbx.count()
|
with open(self.getSettingsPath("profiles.dat"), "rb") as f:
|
||||||
for w_idx in range(w_profileCnt):
|
w_profiles = pickle.load(f)
|
||||||
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
|
|
||||||
if w_jsonProfile == None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
w_profile = json.loads(w_jsonProfile)
|
for w_profile in w_profiles:
|
||||||
w_profiles.append(w_profile)
|
self.ui.profileCbx.addItem(w_profile['name'])
|
||||||
|
w_jsonProfile = json.dumps(w_profile)
|
||||||
|
self.ui.profileCbx.setItemData(self.ui.profileCbx.count() - 1, w_jsonProfile)
|
||||||
|
|
||||||
with open(self.getSettingsPath("profiles.dat"), "wb") as f:
|
return len(w_profiles)
|
||||||
pickle.dump(w_profiles, f, pickle.HIGHEST_PROTOCOL)
|
|
||||||
|
|
||||||
def loadFromDatabase(self):
|
def getSettingsPath(self, setting):
|
||||||
w_profiles = []
|
home = os.path.expanduser("~") + '/.linvam/'
|
||||||
with open(self.getSettingsPath("profiles.dat"), "rb") as f:
|
if not os.path.exists(home):
|
||||||
w_profiles = pickle.load(f)
|
os.mkdir(home)
|
||||||
|
if not os.path.exists(home + setting):
|
||||||
|
shutil.copyfile(setting, home + setting)
|
||||||
|
|
||||||
for w_profile in w_profiles:
|
return home + setting
|
||||||
self.ui.profileCbx.addItem(w_profile['name'])
|
|
||||||
w_jsonProfile = json.dumps(w_profile)
|
|
||||||
self.ui.profileCbx.setItemData(self.ui.profileCbx.count() - 1, w_jsonProfile)
|
|
||||||
|
|
||||||
return len(w_profiles)
|
def loadTestProfiles(self):
|
||||||
|
|
||||||
def getSettingsPath(self, setting):
|
w_carProfileDict = {
|
||||||
home = os.path.expanduser("~") + '/.linvam/'
|
"name": "car game",
|
||||||
if not os.path.exists(home):
|
"commands": [
|
||||||
os.mkdir(home)
|
{'name': 'forward',
|
||||||
if not os.path.exists(home + setting):
|
'actions': [
|
||||||
shutil.copyfile(setting, home + setting)
|
{'name': 'key action', 'key': 'up', 'type': 1}
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 12
|
||||||
|
},
|
||||||
|
{'name': 'back',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'key action', 'key': 'down', 'type': 1}
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'left',
|
||||||
|
'actions': [{'name': 'key action', 'key': 'right', 'type': 0},
|
||||||
|
{'name': 'key action', 'key': 'left', 'type': 1},
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 10
|
||||||
|
},
|
||||||
|
{'name': 'right',
|
||||||
|
'actions': [{'name': 'key action', 'key': 'left', 'type': 0},
|
||||||
|
{'name': 'key action', 'key': 'right', 'type': 1},
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'stop',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'key action', 'key': 'left', 'type': 0},
|
||||||
|
{'name': 'key action', 'key': 'right', 'type': 0},
|
||||||
|
{'name': 'key action', 'key': 'up', 'type': 0},
|
||||||
|
{'name': 'key action', 'key': 'down', 'type': 0}
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return home + setting
|
w_airplaneProfileDict = {
|
||||||
|
"name": "airplane game",
|
||||||
|
"commands": [
|
||||||
|
{'name': 'up',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'command stop action', 'command name': 'down'},
|
||||||
|
{'name': 'mouse move action', 'x': 0, 'y': -5, 'absolute': False},
|
||||||
|
{'name': 'pause action', 'time': 0.01}
|
||||||
|
],
|
||||||
|
'repeat': -1,
|
||||||
|
'async': True,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'left',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'command stop action', 'command name': 'right'},
|
||||||
|
{'name': 'mouse move action', 'x': -5, 'y': 0, 'absolute': False},
|
||||||
|
{'name': 'pause action', 'time': 0.005}
|
||||||
|
],
|
||||||
|
'repeat': -1,
|
||||||
|
'async': True,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'right',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'command stop action', 'command name': 'left'},
|
||||||
|
{'name': 'mouse move action', 'x': 5, 'y': 0, 'absolute': False},
|
||||||
|
{'name': 'pause action', 'time': 0.005}
|
||||||
|
],
|
||||||
|
'repeat': -1,
|
||||||
|
'async': True,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'down',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'command stop action', 'command name': 'up'},
|
||||||
|
{'name': 'mouse move action', 'x': 0, 'y': 5, 'absolute': False},
|
||||||
|
{'name': 'pause action', 'time': 0.005}
|
||||||
|
],
|
||||||
|
'repeat': -1,
|
||||||
|
'async': True,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'shoot',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'mouse click action', 'button': 'left', 'type': 1},
|
||||||
|
{'name': 'pause action', 'time': 0.03}
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 3
|
||||||
|
},
|
||||||
|
{'name': 'stop',
|
||||||
|
'actions': [
|
||||||
|
{'name': 'command stop action', 'command name': 'up'},
|
||||||
|
{'name': 'command stop action', 'command name': 'left'},
|
||||||
|
{'name': 'command stop action', 'command name': 'right'},
|
||||||
|
{'name': 'command stop action', 'command name': 'down'},
|
||||||
|
{'name': 'mouse click action', 'button': 'left', 'type': 0}
|
||||||
|
],
|
||||||
|
'repeat': 1,
|
||||||
|
'async': False,
|
||||||
|
'threshold': 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
w_profiles = []
|
||||||
|
w_profiles.append(w_carProfileDict)
|
||||||
|
w_profiles.append(w_airplaneProfileDict)
|
||||||
|
|
||||||
def loadTestProfiles(self):
|
i = 0
|
||||||
|
for w_profile in w_profiles:
|
||||||
|
self.ui.profileCbx.addItem(w_profile['name'])
|
||||||
|
w_jsonProfile = json.dumps(w_profile)
|
||||||
|
self.ui.profileCbx.setItemData(i, w_jsonProfile)
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
w_carProfileDict = {
|
return i
|
||||||
"name": "car game",
|
|
||||||
"commands": [
|
|
||||||
{'name': 'forward',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'key action', 'key': 'up', 'type': 1}
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 12
|
|
||||||
},
|
|
||||||
{'name': 'back',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'key action', 'key': 'down', 'type': 1}
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'left',
|
|
||||||
'actions': [{'name': 'key action', 'key': 'right', 'type': 0},
|
|
||||||
{'name': 'key action', 'key': 'left', 'type': 1},
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 10
|
|
||||||
},
|
|
||||||
{'name': 'right',
|
|
||||||
'actions': [{'name': 'key action', 'key': 'left', 'type': 0},
|
|
||||||
{'name': 'key action', 'key': 'right', 'type': 1},
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'stop',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'key action', 'key': 'left', 'type': 0},
|
|
||||||
{'name': 'key action', 'key': 'right', 'type': 0},
|
|
||||||
{'name': 'key action', 'key': 'up', 'type': 0},
|
|
||||||
{'name': 'key action', 'key': 'down', 'type': 0}
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 8
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
w_airplaneProfileDict = {
|
def slotProfileChanged(self, p_idx):
|
||||||
"name": "airplane game",
|
w_jsonProfile = self.ui.profileCbx.itemData(p_idx)
|
||||||
"commands": [
|
if w_jsonProfile != None:
|
||||||
{'name': 'up',
|
self.m_activeProfile = json.loads(w_jsonProfile)
|
||||||
'actions': [
|
self.m_profileExecutor.setProfile(self.m_activeProfile)
|
||||||
{'name': 'command stop action', 'command name': 'down'},
|
|
||||||
{'name': 'mouse move action', 'x': 0, 'y': -5, 'absolute': False},
|
|
||||||
{'name': 'pause action', 'time': 0.01}
|
|
||||||
],
|
|
||||||
'repeat': -1,
|
|
||||||
'async': True,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'left',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'command stop action', 'command name': 'right'},
|
|
||||||
{'name': 'mouse move action', 'x': -5, 'y': 0, 'absolute': False},
|
|
||||||
{'name': 'pause action', 'time': 0.005}
|
|
||||||
],
|
|
||||||
'repeat': -1,
|
|
||||||
'async': True,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'right',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'command stop action', 'command name': 'left'},
|
|
||||||
{'name': 'mouse move action', 'x': 5, 'y': 0, 'absolute': False},
|
|
||||||
{'name': 'pause action', 'time': 0.005}
|
|
||||||
],
|
|
||||||
'repeat': -1,
|
|
||||||
'async': True,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'down',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'command stop action', 'command name': 'up'},
|
|
||||||
{'name': 'mouse move action', 'x': 0, 'y': 5, 'absolute': False},
|
|
||||||
{'name': 'pause action', 'time': 0.005}
|
|
||||||
],
|
|
||||||
'repeat': -1,
|
|
||||||
'async': True,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'shoot',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'mouse click action', 'button': 'left', 'type': 1},
|
|
||||||
{'name': 'pause action', 'time': 0.03}
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 3
|
|
||||||
},
|
|
||||||
{'name': 'stop',
|
|
||||||
'actions': [
|
|
||||||
{'name': 'command stop action', 'command name': 'up'},
|
|
||||||
{'name': 'command stop action', 'command name': 'left'},
|
|
||||||
{'name': 'command stop action', 'command name': 'right'},
|
|
||||||
{'name': 'command stop action', 'command name': 'down'},
|
|
||||||
{'name': 'mouse click action', 'button': 'left', 'type': 0}
|
|
||||||
],
|
|
||||||
'repeat': 1,
|
|
||||||
'async': False,
|
|
||||||
'threshold': 3
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
w_profiles = []
|
|
||||||
w_profiles.append(w_carProfileDict)
|
|
||||||
w_profiles.append(w_airplaneProfileDict)
|
|
||||||
|
|
||||||
i = 0
|
def slotAddNewProfile(self):
|
||||||
for w_profile in w_profiles:
|
w_profileEditWnd = ProfileEditWnd(None, self)
|
||||||
self.ui.profileCbx.addItem(w_profile['name'])
|
if w_profileEditWnd.exec() == QDialog.Accepted:
|
||||||
w_jsonProfile = json.dumps(w_profile)
|
w_profile = w_profileEditWnd.m_profile
|
||||||
self.ui.profileCbx.setItemData(i, w_jsonProfile)
|
self.m_profileExecutor.setProfile(w_profile)
|
||||||
i = i + 1
|
self.ui.profileCbx.addItem(w_profile['name'])
|
||||||
|
w_jsonProfile = json.dumps(w_profile)
|
||||||
|
self.ui.profileCbx.setItemData(self.ui.profileCbx.count()-1, w_jsonProfile)
|
||||||
|
|
||||||
return i
|
def slotEditProfile(self):
|
||||||
|
w_idx = self.ui.profileCbx.currentIndex()
|
||||||
|
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
|
||||||
|
w_profile = json.loads(w_jsonProfile)
|
||||||
|
w_profileEditWnd = ProfileEditWnd(w_profile, self)
|
||||||
|
if w_profileEditWnd.exec() == QDialog.Accepted:
|
||||||
|
w_profile = w_profileEditWnd.m_profile
|
||||||
|
self.m_profileExecutor.setProfile(w_profile)
|
||||||
|
self.ui.profileCbx.setItemText(w_idx, w_profile['name'])
|
||||||
|
w_jsonProfile = json.dumps(w_profile)
|
||||||
|
self.ui.profileCbx.setItemData(w_idx, w_jsonProfile)
|
||||||
|
|
||||||
def slotProfileChanged(self, p_idx):
|
def slotRemoveProfile(self):
|
||||||
w_jsonProfile = self.ui.profileCbx.itemData(p_idx)
|
w_curIdx = self.ui.profileCbx.currentIndex()
|
||||||
if w_jsonProfile != None:
|
if w_curIdx >= 0:
|
||||||
self.m_activeProfile = json.loads(w_jsonProfile)
|
self.ui.profileCbx.removeItem(w_curIdx)
|
||||||
self.m_profileExecutor.setProfile(self.m_activeProfile)
|
|
||||||
|
|
||||||
def slotAddNewProfile(self):
|
w_curIdx = self.ui.profileCbx.currentIndex()
|
||||||
w_profileEditWnd = ProfileEditWnd(None, self)
|
if w_curIdx >= 0:
|
||||||
if w_profileEditWnd.exec() == QDialog.Accepted:
|
w_jsonProfile = self.ui.profileCbx.itemData(w_curIdx)
|
||||||
w_profile = w_profileEditWnd.m_profile
|
w_profile = json.loads(w_jsonProfile)
|
||||||
self.m_profileExecutor.setProfile(w_profile)
|
self.m_profileExecutor.setProfile(w_profile)
|
||||||
self.ui.profileCbx.addItem(w_profile['name'])
|
|
||||||
w_jsonProfile = json.dumps(w_profile)
|
|
||||||
self.ui.profileCbx.setItemData(self.ui.profileCbx.count()-1, w_jsonProfile)
|
|
||||||
|
|
||||||
def slotEditProfile(self):
|
def slotListeningEnabled(self, p_enabled):
|
||||||
w_idx = self.ui.profileCbx.currentIndex()
|
if p_enabled:
|
||||||
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
|
self.m_profileExecutor.setEnableListening(True)
|
||||||
w_profile = json.loads(w_jsonProfile)
|
else:
|
||||||
w_profileEditWnd = ProfileEditWnd(w_profile, self)
|
self.m_profileExecutor.setEnableListening(False)
|
||||||
if w_profileEditWnd.exec() == QDialog.Accepted:
|
|
||||||
w_profile = w_profileEditWnd.m_profile
|
|
||||||
self.m_profileExecutor.setProfile(w_profile)
|
|
||||||
self.ui.profileCbx.setItemText(w_idx, w_profile['name'])
|
|
||||||
w_jsonProfile = json.dumps(w_profile)
|
|
||||||
self.ui.profileCbx.setItemData(w_idx, w_jsonProfile)
|
|
||||||
|
|
||||||
def slotCopyProfile(self):
|
|
||||||
text, okPressed = QInputDialog.getText(self, "Copy profile", "Enter new profile name:", QLineEdit.Normal, "")
|
|
||||||
if okPressed and text != '':
|
|
||||||
# todo: check if name not already in use
|
|
||||||
w_idx = self.ui.profileCbx.currentIndex()
|
|
||||||
w_jsonProfile = self.ui.profileCbx.itemData(w_idx)
|
|
||||||
w_profile = json.loads(w_jsonProfile)
|
|
||||||
w_profile_copy = w_profile
|
|
||||||
w_profile_copy['name'] = text
|
|
||||||
self.ui.profileCbx.addItem(w_profile_copy['name'])
|
|
||||||
w_jsonProfile = json.dumps(w_profile_copy)
|
|
||||||
self.ui.profileCbx.setItemData(self.ui.profileCbx.count()-1, w_jsonProfile)
|
|
||||||
|
|
||||||
def slotRemoveProfile(self):
|
|
||||||
|
|
||||||
buttonReply = QMessageBox.question(self, 'Remove Profile', "Do you really want to delete this profile?", QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
|
|
||||||
if buttonReply == QMessageBox.No:
|
|
||||||
return
|
|
||||||
|
|
||||||
w_curIdx = self.ui.profileCbx.currentIndex()
|
|
||||||
if w_curIdx >= 0:
|
|
||||||
self.ui.profileCbx.removeItem(w_curIdx)
|
|
||||||
|
|
||||||
w_curIdx = self.ui.profileCbx.currentIndex()
|
|
||||||
if w_curIdx >= 0:
|
|
||||||
w_jsonProfile = self.ui.profileCbx.itemData(w_curIdx)
|
|
||||||
w_profile = json.loads(w_jsonProfile)
|
|
||||||
self.m_profileExecutor.setProfile(w_profile)
|
|
||||||
|
|
||||||
def slotListeningEnabled(self, p_enabled):
|
|
||||||
if p_enabled:
|
|
||||||
self.m_profileExecutor.setEnableListening(True)
|
|
||||||
else:
|
|
||||||
self.m_profileExecutor.setEnableListening(False)
|
|
||||||
|
|
||||||
def slotOK(self):
|
|
||||||
self.saveToDatabase()
|
|
||||||
self.m_profileExecutor.shutdown()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def slotCancel(self):
|
|
||||||
self.m_profileExecutor.shutdown()
|
|
||||||
self.close()
|
|
||||||
exit()
|
|
||||||
|
|
||||||
def handleArgs(self):
|
|
||||||
self.m_config = {
|
|
||||||
'noroot' : 0,
|
|
||||||
'xdowindowid' : None
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
return
|
|
||||||
|
|
||||||
for i in range(1,len(sys.argv)):
|
|
||||||
if sys.argv[i] == '-noroot':
|
|
||||||
self.m_config['noroot'] = 1
|
|
||||||
elif sys.argv[i] == '-xdowindowid' and (i+1 < len(sys.argv)):
|
|
||||||
self.m_config['xdowindowid'] = sys.argv[i+1]
|
|
||||||
i = i+1
|
|
||||||
|
|
||||||
# try to help: if -noroot is supplied, but no xdowindowid, try to determine the id
|
|
||||||
# Elite Dangerous only
|
|
||||||
try:
|
|
||||||
args = shlex.split('xdotool search --name "\(CLIENT\)"')
|
|
||||||
window_id = str(subprocess.check_output(args))
|
|
||||||
window_id = window_id.replace('b\'', '')
|
|
||||||
window_id = window_id.replace('\\n\'','')
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
window_id = None
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not window_id == None:
|
|
||||||
# check if it's really the Client we want
|
|
||||||
try:
|
|
||||||
window_name = subprocess.check_output(['xdotool', 'getwindowname', str(window_id)])
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
window_name = None
|
|
||||||
pass
|
|
||||||
if not window_name == None:
|
|
||||||
if not str(window_name).find('Elite - Dangerous') == -1:
|
|
||||||
print("Window ID: ", str(window_id), ", window name: ", window_name)
|
|
||||||
print("Auto-detected window id of ED Client: ", window_id)
|
|
||||||
self.m_config['xdowindowid'] = window_id
|
|
||||||
|
|
||||||
|
def slotOK(self):
|
||||||
|
self.saveToDatabase()
|
||||||
|
self.m_profileExecutor.stop()
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def slotCancel(self):
|
||||||
|
self.m_profileExecutor.stop()
|
||||||
|
self.close()
|
||||||
|
exit()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
mainWnd = MainWnd()
|
mainWnd = MainWnd()
|
||||||
mainWnd.show()
|
mainWnd.show()
|
||||||
sys.exit(app.exec_())
|
sys.exit(app.exec_())
|
||||||
|
100
mainwnd.ui
100
mainwnd.ui
@ -25,7 +25,7 @@
|
|||||||
<property name="verticalSpacing">
|
<property name="verticalSpacing">
|
||||||
<number>20</number>
|
<number>20</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="2" column="0">
|
<item row="1" column="0">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>20</number>
|
<number>20</number>
|
||||||
@ -37,57 +37,6 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QGroupBox" name="groupBox">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>200</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="title">
|
|
||||||
<string>Voice Volume</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignCenter</set>
|
|
||||||
</property>
|
|
||||||
<widget class="QSlider" name="sliderVolume">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>10</x>
|
|
||||||
<y>20</y>
|
|
||||||
<width>181</width>
|
|
||||||
<height>21</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Change voice volume</string>
|
|
||||||
</property>
|
|
||||||
<property name="maximum">
|
|
||||||
<number>100</number>
|
|
||||||
</property>
|
|
||||||
<property name="value">
|
|
||||||
<number>100</number>
|
|
||||||
</property>
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<spacer name="horizontalSpacer">
|
<spacer name="horizontalSpacer">
|
||||||
<property name="orientation">
|
<property name="orientation">
|
||||||
@ -95,7 +44,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="sizeHint" stdset="0">
|
<property name="sizeHint" stdset="0">
|
||||||
<size>
|
<size>
|
||||||
<width>20</width>
|
<width>40</width>
|
||||||
<height>20</height>
|
<height>20</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -129,19 +78,13 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item row="1" column="0">
|
<item row="0" column="0">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
<property name="spacing">
|
<property name="spacing">
|
||||||
<number>20</number>
|
<number>20</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>60</width>
|
<width>60</width>
|
||||||
@ -157,7 +100,7 @@
|
|||||||
<widget class="QComboBox" name="profileCbx">
|
<widget class="QComboBox" name="profileCbx">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>300</width>
|
<width>250</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -167,7 +110,7 @@
|
|||||||
<widget class="QPushButton" name="addBut">
|
<widget class="QPushButton" name="addBut">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>75</width>
|
<width>130</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -180,7 +123,7 @@
|
|||||||
<widget class="QPushButton" name="editBut">
|
<widget class="QPushButton" name="editBut">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>75</width>
|
<width>130</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -189,27 +132,11 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="copyBut">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>75</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Copy currently selected profile</string>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Copy</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="removeBut">
|
<widget class="QPushButton" name="removeBut">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>75</width>
|
<width>130</width>
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
@ -218,6 +145,19 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_3">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
|
@ -13,7 +13,6 @@ class ProfileEditWnd(QDialog):
|
|||||||
self.ui = Ui_ProfileEditDialog()
|
self.ui = Ui_ProfileEditDialog()
|
||||||
self.ui.setupUi(self)
|
self.ui.setupUi(self)
|
||||||
self.m_profile = {}
|
self.m_profile = {}
|
||||||
self.m_parent = p_parent
|
|
||||||
|
|
||||||
self.ui.cmdTable.setHorizontalHeaderLabels(('Spoken command', 'Actions'))
|
self.ui.cmdTable.setHorizontalHeaderLabels(('Spoken command', 'Actions'))
|
||||||
self.ui.cmdTable.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.ui.cmdTable.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
|
@ -4,44 +4,31 @@ import time
|
|||||||
import threading
|
import threading
|
||||||
import os, pyaudio
|
import os, pyaudio
|
||||||
import shutil
|
import shutil
|
||||||
import re
|
|
||||||
from pocketsphinx.pocketsphinx import *
|
from pocketsphinx.pocketsphinx import *
|
||||||
from sphinxbase.sphinxbase import *
|
from sphinxbase.sphinxbase import *
|
||||||
from soundfiles import SoundFiles
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileExecutor(threading.Thread):
|
class ProfileExecutor(threading.Thread):
|
||||||
mouse = Controller()
|
mouse = Controller()
|
||||||
|
|
||||||
def __init__(self, p_profile = None, p_parent = None):
|
def __init__(self, p_profile = None):
|
||||||
# threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
# does nothing?
|
|
||||||
self.setProfile(p_profile)
|
self.setProfile(p_profile)
|
||||||
self.m_stop = False
|
self.m_stop = False
|
||||||
self.m_listening = False
|
self.m_listening = True
|
||||||
self.m_cmdThreads = {}
|
self.m_cmdThreads = {}
|
||||||
|
|
||||||
self.m_config = Decoder.default_config()
|
self.m_config = Decoder.default_config()
|
||||||
self.m_config.set_string('-hmm', os.path.join('model', 'en-us/en-us'))
|
self.m_config.set_string('-hmm', os.path.join('model', 'en-us/en-us'))
|
||||||
self.m_config.set_string('-dict', os.path.join('model', 'en-us/cmudict-en-us.dict'))
|
self.m_config.set_string('-dict', os.path.join('model', 'en-us/cmudict-en-us.dict'))
|
||||||
self.m_config.set_string('-kws', 'command.list')
|
self.m_config.set_string('-kws', 'command.list')
|
||||||
# you usually don't want all this info stuff as a regular user. it just covers up init messages
|
|
||||||
self.m_config.set_string('-logfn', '/dev/null')
|
|
||||||
|
|
||||||
self.m_pyaudio = pyaudio.PyAudio()
|
p = pyaudio.PyAudio()
|
||||||
self.m_stream = self.m_pyaudio.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
|
self.m_stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
|
||||||
|
self.m_stream.start_stream()
|
||||||
|
|
||||||
# Process audio chunk by chunk. On keyword detected perform action and restart search
|
# Process audio chunk by chunk. On keyword detected perform action and restart search
|
||||||
self.m_decoder = Decoder(self.m_config)
|
self.m_decoder = Decoder(self.m_config)
|
||||||
|
|
||||||
self.m_thread = False
|
|
||||||
|
|
||||||
self.p_parent = p_parent
|
|
||||||
if not self.p_parent == None:
|
|
||||||
self.m_sound = self.p_parent.m_sound
|
|
||||||
|
|
||||||
|
|
||||||
def getSettingsPath(self, setting):
|
def getSettingsPath(self, setting):
|
||||||
home = os.path.expanduser("~") + '/.linvam/'
|
home = os.path.expanduser("~") + '/.linvam/'
|
||||||
if not os.path.exists(home):
|
if not os.path.exists(home):
|
||||||
@ -52,46 +39,25 @@ class ProfileExecutor(threading.Thread):
|
|||||||
return home + setting
|
return home + setting
|
||||||
|
|
||||||
def setProfile(self, p_profile):
|
def setProfile(self, p_profile):
|
||||||
#print("setProfile")
|
|
||||||
self.m_profile = p_profile
|
self.m_profile = p_profile
|
||||||
if self.m_profile == None:
|
if self.m_profile == None:
|
||||||
return
|
return
|
||||||
#print ("writing command list")
|
|
||||||
w_commandWordFile = open(self.getSettingsPath('command.list'), 'w')
|
w_commandWordFile = open(self.getSettingsPath('command.list'), 'w')
|
||||||
w_commands = self.m_profile['commands']
|
w_commands = self.m_profile['commands']
|
||||||
i = 0
|
i = 0
|
||||||
for w_command in w_commands:
|
for w_command in w_commands:
|
||||||
if i != 0:
|
if i != 0:
|
||||||
w_commandWordFile.write('\n')
|
w_commandWordFile.write('\n')
|
||||||
w_commandWordFile.write(w_command['name'].lower() + ' /1e-%d/' % w_command['threshold'])
|
w_commandWordFile.write(w_command['name'] + ' /1e-%d/' % w_command['threshold'])
|
||||||
i = i + 1
|
i = i + 1
|
||||||
w_commandWordFile.close()
|
w_commandWordFile.close()
|
||||||
self.m_config.set_string('-kws', self.getSettingsPath('command.list'))
|
self.m_config.set_string('-kws', self.getSettingsPath('command.list'))
|
||||||
# load new command list into decoder and restart it
|
|
||||||
if self.m_listening == True:
|
|
||||||
self.stop()
|
|
||||||
self.m_stream.start_stream()
|
|
||||||
# a self.m_decoder.reinit(self.config) will segfault?
|
|
||||||
self.m_decoder = Decoder(self.m_config)
|
|
||||||
self.m_stop = False
|
|
||||||
self.m_thread = threading.Thread(target=self.doListen, args=())
|
|
||||||
self.m_thread.start()
|
|
||||||
else:
|
|
||||||
self.m_decoder.reinit(self.m_config)
|
|
||||||
|
|
||||||
def setEnableListening(self, p_enable):
|
def setEnableListening(self, p_enable):
|
||||||
if self.m_listening == False and p_enable == True:
|
self.m_listening = p_enable
|
||||||
self.m_stream.start_stream()
|
|
||||||
self.m_listening = p_enable
|
|
||||||
self.m_stop = False
|
|
||||||
self.m_thread = threading.Thread(target=self.doListen, args=())
|
|
||||||
self.m_thread.start()
|
|
||||||
elif self.m_listening == True and p_enable == False:
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
def doListen(self):
|
def run(self):
|
||||||
print("Detection started")
|
|
||||||
self.m_listening = True
|
|
||||||
self.m_decoder.start_utt()
|
self.m_decoder.start_utt()
|
||||||
while self.m_stop != True:
|
while self.m_stop != True:
|
||||||
buf = self.m_stream.read(1024)
|
buf = self.m_stream.read(1024)
|
||||||
@ -99,14 +65,8 @@ class ProfileExecutor(threading.Thread):
|
|||||||
self.m_decoder.process_raw(buf, False, False)
|
self.m_decoder.process_raw(buf, False, False)
|
||||||
|
|
||||||
if self.m_decoder.hyp() != None:
|
if self.m_decoder.hyp() != None:
|
||||||
#print([(seg.word, seg.prob, seg.start_frame, seg.end_frame) for seg in self.m_decoder.seg()])
|
# print([(seg.word, seg.prob, seg.start_frame, seg.end_frame) for seg in decoder.seg()])
|
||||||
#print("Detected keyword, restarting search")
|
# print("Detected keyword, restarting search")
|
||||||
|
|
||||||
# hack :)
|
|
||||||
for seg in self.m_decoder.seg():
|
|
||||||
print("Detected: ",seg.word)
|
|
||||||
break
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Here you run the code you want based on keyword
|
# Here you run the code you want based on keyword
|
||||||
#
|
#
|
||||||
@ -116,21 +76,9 @@ class ProfileExecutor(threading.Thread):
|
|||||||
self.m_decoder.end_utt()
|
self.m_decoder.end_utt()
|
||||||
self.m_decoder.start_utt()
|
self.m_decoder.start_utt()
|
||||||
|
|
||||||
# def run(self):
|
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
if self.m_listening == True:
|
self.m_stop = True
|
||||||
self.m_stop = True
|
threading.Thread.join(self)
|
||||||
self.m_listening = False
|
|
||||||
self.m_decoder.end_utt()
|
|
||||||
self.m_thread.join()
|
|
||||||
self.m_stream.stop_stream()
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
self.stop()
|
|
||||||
self.m_stream.close()
|
|
||||||
self.m_pyaudio.terminate()
|
|
||||||
|
|
||||||
|
|
||||||
def doAction(self, p_action):
|
def doAction(self, p_action):
|
||||||
# {'name': 'key action', 'key': 'left', 'type': 0}
|
# {'name': 'key action', 'key': 'left', 'type': 0}
|
||||||
@ -143,14 +91,20 @@ class ProfileExecutor(threading.Thread):
|
|||||||
if w_actionName == 'key action':
|
if w_actionName == 'key action':
|
||||||
w_key = p_action['key']
|
w_key = p_action['key']
|
||||||
w_type = p_action['type']
|
w_type = p_action['type']
|
||||||
self.pressKey(w_key, w_type)
|
if w_type == 1:
|
||||||
|
keyboard.press(w_key)
|
||||||
|
print("pressed key: ", w_key)
|
||||||
|
elif w_type == 0:
|
||||||
|
keyboard.release(w_key)
|
||||||
|
print("released key: ", w_key)
|
||||||
|
elif w_type == 10:
|
||||||
|
keyboard.press(w_key)
|
||||||
|
keyboard.release(w_key)
|
||||||
|
print("pressed and released key: ", w_key)
|
||||||
elif w_actionName == 'pause action':
|
elif w_actionName == 'pause action':
|
||||||
print("Sleep ", p_action['time'])
|
|
||||||
time.sleep(p_action['time'])
|
time.sleep(p_action['time'])
|
||||||
elif w_actionName == 'command stop action':
|
elif w_actionName == 'command stop action':
|
||||||
self.stopCommand(p_action['command name'])
|
self.stopCommand(p_action['command name'])
|
||||||
elif w_actionName == 'command play sound' or w_actionName == 'play sound':
|
|
||||||
self.playSound(p_action)
|
|
||||||
elif w_actionName == 'mouse move action':
|
elif w_actionName == 'mouse move action':
|
||||||
if p_action['absolute']:
|
if p_action['absolute']:
|
||||||
ProfileExecutor.mouse.position([p_action['x'], p_action['y']])
|
ProfileExecutor.mouse.position([p_action['x'], p_action['y']])
|
||||||
@ -213,7 +167,7 @@ class ProfileExecutor(threading.Thread):
|
|||||||
w_commands = self.m_profile['commands']
|
w_commands = self.m_profile['commands']
|
||||||
flag = False
|
flag = False
|
||||||
for w_command in w_commands:
|
for w_command in w_commands:
|
||||||
if w_command['name'].lower() == p_cmdName:
|
if w_command['name'] == p_cmdName:
|
||||||
flag = True
|
flag = True
|
||||||
break
|
break
|
||||||
if flag == False:
|
if flag == False:
|
||||||
@ -241,66 +195,3 @@ class ProfileExecutor(threading.Thread):
|
|||||||
if p_cmdName in self.m_cmdThreads.keys():
|
if p_cmdName in self.m_cmdThreads.keys():
|
||||||
self.m_cmdThreads[p_cmdName].stop()
|
self.m_cmdThreads[p_cmdName].stop()
|
||||||
del self.m_cmdThreads[p_cmdName]
|
del self.m_cmdThreads[p_cmdName]
|
||||||
|
|
||||||
def playSound(self, p_cmdName):
|
|
||||||
sound_file = './voicepacks/' + p_cmdName['pack'] + '/' + p_cmdName['cat'] + '/' + p_cmdName['file']
|
|
||||||
self.m_sound.play(sound_file)
|
|
||||||
|
|
||||||
|
|
||||||
def pressKey(self, w_key, w_type):
|
|
||||||
if self.p_parent.m_config['noroot'] == 1:
|
|
||||||
# xdotool has a different key mapping. translate old existing mappings of special keys
|
|
||||||
# use this to find key name: xev -event keyboard
|
|
||||||
w_key = re.sub('left ctrl', 'Control_L', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('right ctrl', 'Control_R', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('left shift', 'Shift_L', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('right shift', 'Shift_R', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('left alt', 'Alt_L', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('right alt', 'Alt_R', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('left windows', 'Super_L', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('right windows', 'Super_R', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('tab', 'Tab', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('esc', 'Escape', w_key, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
w_key = re.sub('left', 'Left', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('right', 'Right', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('up', 'Up', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('down', 'Down', w_key, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
w_key = re.sub('ins$', 'Insert', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('del$', 'Delete', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('home', 'Home', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('end', 'End', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('Page\s?up', 'Prior', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('Page\s?down', 'Next', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('return', 'Return', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('enter', 'Return', w_key, flags=re.IGNORECASE)
|
|
||||||
w_key = re.sub('backspace', 'BackSpace', w_key, flags=re.IGNORECASE)
|
|
||||||
|
|
||||||
w_key = w_key.replace('insert', 'Insert')
|
|
||||||
w_key = w_key.replace('delete', 'Delete')
|
|
||||||
|
|
||||||
window_cmd = ""
|
|
||||||
if not self.p_parent.m_config['xdowindowid'] == None:
|
|
||||||
window_cmd = " windowactivate --sync " + str(self.p_parent.m_config['xdowindowid'])
|
|
||||||
|
|
||||||
if w_type == 1:
|
|
||||||
os.system('xdotool ' + window_cmd + ' keydown ' + str(w_key) )
|
|
||||||
print("pressed key: ", w_key)
|
|
||||||
elif w_type == 0:
|
|
||||||
os.system('xdotool' + window_cmd + ' keyup ' + str(w_key))
|
|
||||||
print("released key: ", w_key)
|
|
||||||
elif w_type == 10:
|
|
||||||
os.system('xdotool' + window_cmd + ' key ' + str(w_key))
|
|
||||||
print("pressed and released key: ", w_key)
|
|
||||||
else:
|
|
||||||
if w_type == 1:
|
|
||||||
keyboard.press(w_key)
|
|
||||||
print("pressed key: ", w_key)
|
|
||||||
elif w_type == 0:
|
|
||||||
keyboard.release(w_key)
|
|
||||||
print("released key: ", w_key)
|
|
||||||
elif w_type == 10:
|
|
||||||
keyboard.press(w_key)
|
|
||||||
keyboard.release(w_key)
|
|
||||||
print("pressed and released key: ", w_key)
|
|
@ -1,164 +0,0 @@
|
|||||||
from PyQt5.QtCore import *
|
|
||||||
from PyQt5.QtGui import *
|
|
||||||
from PyQt5.QtWidgets import *
|
|
||||||
import re
|
|
||||||
import os
|
|
||||||
from ui_soundactioneditwnd import Ui_SoundSelect
|
|
||||||
|
|
||||||
class SoundActionEditWnd(QDialog):
|
|
||||||
def __init__(self, p_sounds, p_soundAction = None, p_parent = None):
|
|
||||||
super().__init__(p_parent)
|
|
||||||
self.ui = Ui_SoundSelect()
|
|
||||||
self.ui.setupUi(self)
|
|
||||||
|
|
||||||
if p_sounds == None:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.p_sounds = p_sounds
|
|
||||||
self.selectedVoicepack = False
|
|
||||||
self.selectedCategory = False
|
|
||||||
self.selectedFile = False
|
|
||||||
|
|
||||||
self.ui.buttonOkay.clicked.connect(self.slotOK)
|
|
||||||
self.ui.buttonCancel.clicked.connect(super().reject)
|
|
||||||
self.ui.buttonPlaySound.clicked.connect(self.playSound)
|
|
||||||
self.ui.buttonStopSound.clicked.connect(self.stopSound)
|
|
||||||
self.ui.buttonOkay.setEnabled(False)
|
|
||||||
|
|
||||||
# restore stuff when editing
|
|
||||||
if not p_soundAction == None:
|
|
||||||
self.selectedVoicepack = p_soundAction['pack']
|
|
||||||
self.selectedCategory = p_soundAction['cat']
|
|
||||||
self.selectedFile = p_soundAction['file']
|
|
||||||
self.ui.buttonOkay.setEnabled(True)
|
|
||||||
|
|
||||||
self.listVoicepacks_model = QStandardItemModel()
|
|
||||||
self.ui.listVoicepacks.setModel(self.listVoicepacks_model)
|
|
||||||
self.ui.listVoicepacks.clicked.connect(self.onVoicepackSelect)
|
|
||||||
|
|
||||||
self.listCategories_model = QStandardItemModel()
|
|
||||||
self.ui.listCategories.setModel(self.listCategories_model)
|
|
||||||
self.ui.listCategories.clicked.connect(self.onCategorySelect)
|
|
||||||
|
|
||||||
self.listFiles_model = QStandardItemModel()
|
|
||||||
self.ui.listFiles.setModel(self.listFiles_model)
|
|
||||||
self.ui.listFiles.clicked.connect(self.onFileSelect)
|
|
||||||
self.ui.listFiles.doubleClicked.connect(self.selectAndPlay)
|
|
||||||
|
|
||||||
s = sorted(p_sounds.m_sounds)
|
|
||||||
for v in s:
|
|
||||||
item = QStandardItem(v)
|
|
||||||
self.listVoicepacks_model.appendRow(item)
|
|
||||||
|
|
||||||
self.ui.filterCategories.textChanged.connect(self.populateCategories)
|
|
||||||
self.ui.filterFiles.textChanged.connect(self.populateFiles)
|
|
||||||
|
|
||||||
|
|
||||||
self.populateCategories(False)
|
|
||||||
self.populateFiles(False)
|
|
||||||
|
|
||||||
# when editing, select old entries
|
|
||||||
if not self.selectedVoicepack == False:
|
|
||||||
item = self.listVoicepacks_model.findItems(self.selectedVoicepack)
|
|
||||||
if len(item) > 0:
|
|
||||||
index = self.listVoicepacks_model.indexFromItem(item[0])
|
|
||||||
self.ui.listVoicepacks.setCurrentIndex(index)
|
|
||||||
|
|
||||||
if not self.selectedCategory == False:
|
|
||||||
item = self.listCategories_model.findItems(self.selectedCategory)
|
|
||||||
if len(item) > 0:
|
|
||||||
index = self.listCategories_model.indexFromItem(item[0])
|
|
||||||
self.ui.listCategories.setCurrentIndex(index)
|
|
||||||
|
|
||||||
if not self.selectedFile == False:
|
|
||||||
item = self.listFiles_model.findItems(self.selectedFile)
|
|
||||||
if len(item) > 0:
|
|
||||||
index = self.listFiles_model.indexFromItem(item[0])
|
|
||||||
self.ui.listFiles.setCurrentIndex(index)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def slotOK(self):
|
|
||||||
self.m_soundAction = {'name': 'play sound', 'pack': self.selectedVoicepack, 'cat' : self.selectedCategory, 'file' : self.selectedFile}
|
|
||||||
super().accept()
|
|
||||||
|
|
||||||
def slotCancel(self):
|
|
||||||
super().reject()
|
|
||||||
|
|
||||||
def onVoicepackSelect(self):
|
|
||||||
index = self.ui.listVoicepacks.currentIndex()
|
|
||||||
itemText = index.data()
|
|
||||||
self.selectedVoicepack = itemText
|
|
||||||
self.populateCategories()
|
|
||||||
self.ui.buttonOkay.setEnabled(False)
|
|
||||||
self.ui.buttonPlaySound.setEnabled(False)
|
|
||||||
|
|
||||||
def onCategorySelect(self):
|
|
||||||
index = self.ui.listCategories.currentIndex()
|
|
||||||
itemText = index.data()
|
|
||||||
self.selectedCategory = itemText
|
|
||||||
self.populateFiles()
|
|
||||||
self.ui.buttonOkay.setEnabled(False)
|
|
||||||
self.ui.buttonPlaySound.setEnabled(False)
|
|
||||||
|
|
||||||
|
|
||||||
def onFileSelect(self):
|
|
||||||
index = self.ui.listFiles.currentIndex()
|
|
||||||
itemText = index.data()
|
|
||||||
self.selectedFile = itemText
|
|
||||||
self.ui.buttonOkay.setEnabled(True)
|
|
||||||
self.ui.buttonPlaySound.setEnabled(True)
|
|
||||||
|
|
||||||
def selectAndPlay(self):
|
|
||||||
self.onFileSelect()
|
|
||||||
self.playSound()
|
|
||||||
|
|
||||||
def populateCategories(self, reset = True):
|
|
||||||
if self.selectedVoicepack == False:
|
|
||||||
return
|
|
||||||
|
|
||||||
if reset == True:
|
|
||||||
self.listCategories_model.removeRows( 0, self.listCategories_model.rowCount() )
|
|
||||||
self.listFiles_model.removeRows( 0, self.listFiles_model.rowCount() )
|
|
||||||
self.selectedCategory = False
|
|
||||||
self.selectedFile = False
|
|
||||||
|
|
||||||
filter_categories = self.ui.filterCategories.toPlainText()
|
|
||||||
if len(filter_categories) == 0:
|
|
||||||
filter_categories = None
|
|
||||||
|
|
||||||
s = sorted(self.p_sounds.m_sounds[self.selectedVoicepack])
|
|
||||||
for v in s:
|
|
||||||
if not filter_categories == None:
|
|
||||||
if not re.search(filter_categories, v, re.IGNORECASE):
|
|
||||||
continue
|
|
||||||
item = QStandardItem(v)
|
|
||||||
self.listCategories_model.appendRow(item)
|
|
||||||
|
|
||||||
def populateFiles(self, reset = True):
|
|
||||||
if self.selectedVoicepack == False or self.selectedCategory == False:
|
|
||||||
return
|
|
||||||
|
|
||||||
if reset == True:
|
|
||||||
self.listFiles_model.removeRows( 0, self.listFiles_model.rowCount() )
|
|
||||||
self.selectedFile = False
|
|
||||||
|
|
||||||
filter_files = self.ui.filterFiles.toPlainText()
|
|
||||||
if len(filter_files) == 0:
|
|
||||||
filter_files = None
|
|
||||||
|
|
||||||
s = sorted(self.p_sounds.m_sounds[self.selectedVoicepack][self.selectedCategory])
|
|
||||||
for v in s:
|
|
||||||
if not filter_files == None:
|
|
||||||
if not re.search(filter_files, v, re.IGNORECASE):
|
|
||||||
continue
|
|
||||||
item = QStandardItem(v)
|
|
||||||
self.listFiles_model.appendRow(item)
|
|
||||||
|
|
||||||
def playSound(self):
|
|
||||||
sound_file = './voicepacks/' + self.selectedVoicepack + '/' + self.selectedCategory + '/' + self.selectedFile
|
|
||||||
self.p_sounds.play(sound_file)
|
|
||||||
|
|
||||||
def stopSound(self):
|
|
||||||
self.p_sounds.stop()
|
|
@ -1,241 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>SoundSelect</class>
|
|
||||||
<widget class="QGroupBox" name="SoundSelect">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>1119</width>
|
|
||||||
<height>362</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Sound selection</string>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<widget class="QLabel" name="labelVoicepacks">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>10</x>
|
|
||||||
<y>30</y>
|
|
||||||
<width>111</width>
|
|
||||||
<height>17</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>VoicePacks:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QLabel" name="labelCategories">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>160</x>
|
|
||||||
<y>30</y>
|
|
||||||
<width>111</width>
|
|
||||||
<height>17</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Categories:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QListView" name="listFiles">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>660</x>
|
|
||||||
<y>90</y>
|
|
||||||
<width>451</width>
|
|
||||||
<height>231</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="showDropIndicator" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QListView" name="listCategories">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>160</x>
|
|
||||||
<y>90</y>
|
|
||||||
<width>491</width>
|
|
||||||
<height>231</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="showDropIndicator" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QListView" name="listVoicepacks">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>10</x>
|
|
||||||
<y>50</y>
|
|
||||||
<width>141</width>
|
|
||||||
<height>271</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::NoEditTriggers</set>
|
|
||||||
</property>
|
|
||||||
<property name="showDropIndicator" stdset="0">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="alternatingRowColors">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QLabel" name="labelFiles">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>660</x>
|
|
||||||
<y>30</y>
|
|
||||||
<width>111</width>
|
|
||||||
<height>17</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Voice files:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QPushButton" name="buttonOkay">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>940</x>
|
|
||||||
<y>330</y>
|
|
||||||
<width>80</width>
|
|
||||||
<height>25</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Ok</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QPushButton" name="buttonCancel">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>1030</x>
|
|
||||||
<y>330</y>
|
|
||||||
<width>80</width>
|
|
||||||
<height>25</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="locale">
|
|
||||||
<locale language="English" country="UnitedStates"/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Cancel</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QPlainTextEdit" name="filterCategories">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>160</x>
|
|
||||||
<y>50</y>
|
|
||||||
<width>491</width>
|
|
||||||
<height>31</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Filter categories</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Filter</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QPlainTextEdit" name="filterFiles">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>660</x>
|
|
||||||
<y>50</y>
|
|
||||||
<width>451</width>
|
|
||||||
<height>31</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Filter voice files</string>
|
|
||||||
</property>
|
|
||||||
<property name="statusTip">
|
|
||||||
<string>Filter</string>
|
|
||||||
</property>
|
|
||||||
<property name="plainText">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QPushButton" name="buttonPlaySound">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>410</x>
|
|
||||||
<y>330</y>
|
|
||||||
<width>80</width>
|
|
||||||
<height>25</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Play Sound</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QPushButton" name="buttonStopSound">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>500</x>
|
|
||||||
<y>330</y>
|
|
||||||
<width>80</width>
|
|
||||||
<height>25</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Stop Sound</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
@ -1,87 +0,0 @@
|
|||||||
import time
|
|
||||||
import threading
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
from os import path
|
|
||||||
import subprocess
|
|
||||||
import shlex
|
|
||||||
import signal
|
|
||||||
|
|
||||||
# I've tried a couple of libs that are capable of playing mp3:
|
|
||||||
# pysound - offers no way to stop sounds. can't play files with whitespace in path
|
|
||||||
# pygame - has problems with certain mp3 files from voice packs
|
|
||||||
# mpg123 - does not offer volume control. need to be threaded as it's a system binary
|
|
||||||
#
|
|
||||||
# ffplay - need to be threaded as it's a system binary. i picked this one as it uses ffmpeg which
|
|
||||||
# is an excellent tool and should be installed on any system already
|
|
||||||
# I needed some process stuff to be able to stop an already playing sound (kill ffplay subprocess)
|
|
||||||
|
|
||||||
|
|
||||||
class SoundFiles():
|
|
||||||
def __init__(self):
|
|
||||||
print("SoundFiles: init")
|
|
||||||
self.m_sounds = {}
|
|
||||||
self.scanSoundFiles()
|
|
||||||
self.thread_play = False
|
|
||||||
self.volume = 100
|
|
||||||
|
|
||||||
def scanSoundFiles(self):
|
|
||||||
print("SoundFiles: scanning")
|
|
||||||
if not path.exists('./voicepacks'):
|
|
||||||
print("No folder 'voicepacks' found. Please create one and copy all your voicepacks in there.")
|
|
||||||
return
|
|
||||||
|
|
||||||
for root, dirs, files in os.walk("./voicepacks"):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith(".mp3"):
|
|
||||||
# we expect a path like this:
|
|
||||||
# voicepacks/VOICEPACKNAME/COMMANDGROUP/(FURTHER_OPTIONAL_FOLDERS/)FILE
|
|
||||||
path_parts = root.split('/')
|
|
||||||
|
|
||||||
if len(path_parts) < 4:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not path_parts[2] in self.m_sounds:
|
|
||||||
self.m_sounds[path_parts[2]] = {}
|
|
||||||
|
|
||||||
category = path_parts[3]
|
|
||||||
# there might be subfolders, so we have more than just 4 split results...
|
|
||||||
# for the ease of my mind, we concat the voicepack subfolders to 1 category name
|
|
||||||
# like voicepacks/hcspack/Characters/Astra/blah.mp3 will become:
|
|
||||||
#
|
|
||||||
# voicepack = hcspack
|
|
||||||
# category = Characters/Astra
|
|
||||||
# file = blah.mp4
|
|
||||||
|
|
||||||
if len(path_parts) > 4:
|
|
||||||
for i in range(4, len(path_parts)):
|
|
||||||
category = category + '/' + path_parts[i]
|
|
||||||
|
|
||||||
if not category in self.m_sounds[path_parts[2]]:
|
|
||||||
self.m_sounds[path_parts[2]][category] = []
|
|
||||||
|
|
||||||
self.m_sounds[path_parts[2]][category].append(file)
|
|
||||||
|
|
||||||
def play(self, sound_file):
|
|
||||||
if not os.path.isfile(sound_file):
|
|
||||||
print("ERROR - Sound file not found: ", sound_file)
|
|
||||||
return
|
|
||||||
|
|
||||||
self.stop()
|
|
||||||
|
|
||||||
# construct shell command. use shlex to split it up into valid args for Popen.
|
|
||||||
cmd = "ffplay -nodisp -autoexit -loglevel quiet -volume " + str(self.volume) + " \"" + sound_file + "\"";
|
|
||||||
args = shlex.split(cmd)
|
|
||||||
self.thread_play = subprocess.Popen(args)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
if not self.thread_play == False:
|
|
||||||
# that aint no nice, but it's the only way i got the subprocess reliably killed.
|
|
||||||
# self.thread_play.terminate() or kill() should do the trick, but it won't
|
|
||||||
try:
|
|
||||||
os.kill(self.thread_play.pid, signal.SIGKILL)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setVolume(self, volume):
|
|
||||||
self.volume = volume;
|
|
@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file 'mainwnd.ui'
|
# Form implementation generated from reading ui file 'mainwnd.ui'
|
||||||
#
|
#
|
||||||
# Created by: PyQt5 UI code generator 5.14.2
|
# Created by: PyQt5 UI code generator 5.12.1
|
||||||
#
|
#
|
||||||
# WARNING! All changes made in this file will be lost!
|
# WARNING! All changes made in this file will be lost!
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
|
||||||
@ -29,19 +28,6 @@ class Ui_MainWidget(object):
|
|||||||
self.horizontalLayout_2.addWidget(self.listeningChk)
|
self.horizontalLayout_2.addWidget(self.listeningChk)
|
||||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||||
self.horizontalLayout_2.addItem(spacerItem)
|
self.horizontalLayout_2.addItem(spacerItem)
|
||||||
self.groupBox = QtWidgets.QGroupBox(MainWidget)
|
|
||||||
self.groupBox.setMinimumSize(QtCore.QSize(200, 42))
|
|
||||||
self.groupBox.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
self.groupBox.setObjectName("groupBox")
|
|
||||||
self.sliderVolume = QtWidgets.QSlider(self.groupBox)
|
|
||||||
self.sliderVolume.setGeometry(QtCore.QRect(10, 20, 181, 21))
|
|
||||||
self.sliderVolume.setMaximum(100)
|
|
||||||
self.sliderVolume.setProperty("value", 100)
|
|
||||||
self.sliderVolume.setOrientation(QtCore.Qt.Horizontal)
|
|
||||||
self.sliderVolume.setObjectName("sliderVolume")
|
|
||||||
self.horizontalLayout_2.addWidget(self.groupBox)
|
|
||||||
spacerItem1 = QtWidgets.QSpacerItem(20, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
|
||||||
self.horizontalLayout_2.addItem(spacerItem1)
|
|
||||||
self.ok = QtWidgets.QPushButton(MainWidget)
|
self.ok = QtWidgets.QPushButton(MainWidget)
|
||||||
self.ok.setMinimumSize(QtCore.QSize(130, 0))
|
self.ok.setMinimumSize(QtCore.QSize(130, 0))
|
||||||
self.ok.setObjectName("ok")
|
self.ok.setObjectName("ok")
|
||||||
@ -50,40 +36,33 @@ class Ui_MainWidget(object):
|
|||||||
self.cancel.setMinimumSize(QtCore.QSize(130, 0))
|
self.cancel.setMinimumSize(QtCore.QSize(130, 0))
|
||||||
self.cancel.setObjectName("cancel")
|
self.cancel.setObjectName("cancel")
|
||||||
self.horizontalLayout_2.addWidget(self.cancel)
|
self.horizontalLayout_2.addWidget(self.cancel)
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout_2, 2, 0, 1, 1)
|
self.gridLayout_2.addLayout(self.horizontalLayout_2, 1, 0, 1, 1)
|
||||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||||
self.horizontalLayout.setSpacing(20)
|
self.horizontalLayout.setSpacing(20)
|
||||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||||
self.label = QtWidgets.QLabel(MainWidget)
|
self.label = QtWidgets.QLabel(MainWidget)
|
||||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
|
|
||||||
sizePolicy.setHorizontalStretch(0)
|
|
||||||
sizePolicy.setVerticalStretch(0)
|
|
||||||
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
|
|
||||||
self.label.setSizePolicy(sizePolicy)
|
|
||||||
self.label.setMinimumSize(QtCore.QSize(60, 0))
|
self.label.setMinimumSize(QtCore.QSize(60, 0))
|
||||||
self.label.setObjectName("label")
|
self.label.setObjectName("label")
|
||||||
self.horizontalLayout.addWidget(self.label)
|
self.horizontalLayout.addWidget(self.label)
|
||||||
self.profileCbx = QtWidgets.QComboBox(MainWidget)
|
self.profileCbx = QtWidgets.QComboBox(MainWidget)
|
||||||
self.profileCbx.setMinimumSize(QtCore.QSize(300, 0))
|
self.profileCbx.setMinimumSize(QtCore.QSize(250, 0))
|
||||||
self.profileCbx.setObjectName("profileCbx")
|
self.profileCbx.setObjectName("profileCbx")
|
||||||
self.horizontalLayout.addWidget(self.profileCbx)
|
self.horizontalLayout.addWidget(self.profileCbx)
|
||||||
self.addBut = QtWidgets.QPushButton(MainWidget)
|
self.addBut = QtWidgets.QPushButton(MainWidget)
|
||||||
self.addBut.setMinimumSize(QtCore.QSize(75, 0))
|
self.addBut.setMinimumSize(QtCore.QSize(130, 0))
|
||||||
self.addBut.setObjectName("addBut")
|
self.addBut.setObjectName("addBut")
|
||||||
self.horizontalLayout.addWidget(self.addBut)
|
self.horizontalLayout.addWidget(self.addBut)
|
||||||
self.editBut = QtWidgets.QPushButton(MainWidget)
|
self.editBut = QtWidgets.QPushButton(MainWidget)
|
||||||
self.editBut.setMinimumSize(QtCore.QSize(75, 0))
|
self.editBut.setMinimumSize(QtCore.QSize(130, 0))
|
||||||
self.editBut.setObjectName("editBut")
|
self.editBut.setObjectName("editBut")
|
||||||
self.horizontalLayout.addWidget(self.editBut)
|
self.horizontalLayout.addWidget(self.editBut)
|
||||||
self.copyBut = QtWidgets.QPushButton(MainWidget)
|
|
||||||
self.copyBut.setMinimumSize(QtCore.QSize(75, 0))
|
|
||||||
self.copyBut.setObjectName("copyBut")
|
|
||||||
self.horizontalLayout.addWidget(self.copyBut)
|
|
||||||
self.removeBut = QtWidgets.QPushButton(MainWidget)
|
self.removeBut = QtWidgets.QPushButton(MainWidget)
|
||||||
self.removeBut.setMinimumSize(QtCore.QSize(75, 0))
|
self.removeBut.setMinimumSize(QtCore.QSize(130, 0))
|
||||||
self.removeBut.setObjectName("removeBut")
|
self.removeBut.setObjectName("removeBut")
|
||||||
self.horizontalLayout.addWidget(self.removeBut)
|
self.horizontalLayout.addWidget(self.removeBut)
|
||||||
self.gridLayout_2.addLayout(self.horizontalLayout, 1, 0, 1, 1)
|
spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||||
|
self.horizontalLayout.addItem(spacerItem1)
|
||||||
|
self.gridLayout_2.addLayout(self.horizontalLayout, 0, 0, 1, 1)
|
||||||
|
|
||||||
self.retranslateUi(MainWidget)
|
self.retranslateUi(MainWidget)
|
||||||
QtCore.QMetaObject.connectSlotsByName(MainWidget)
|
QtCore.QMetaObject.connectSlotsByName(MainWidget)
|
||||||
@ -92,13 +71,21 @@ class Ui_MainWidget(object):
|
|||||||
_translate = QtCore.QCoreApplication.translate
|
_translate = QtCore.QCoreApplication.translate
|
||||||
MainWidget.setWindowTitle(_translate("MainWidget", "LinVAM"))
|
MainWidget.setWindowTitle(_translate("MainWidget", "LinVAM"))
|
||||||
self.listeningChk.setText(_translate("MainWidget", "Enable Listening"))
|
self.listeningChk.setText(_translate("MainWidget", "Enable Listening"))
|
||||||
self.groupBox.setTitle(_translate("MainWidget", "Voice Volume"))
|
|
||||||
self.sliderVolume.setToolTip(_translate("MainWidget", "Change voice volume"))
|
|
||||||
self.ok.setText(_translate("MainWidget", "OK"))
|
self.ok.setText(_translate("MainWidget", "OK"))
|
||||||
self.cancel.setText(_translate("MainWidget", "Cancel"))
|
self.cancel.setText(_translate("MainWidget", "Cancel"))
|
||||||
self.label.setText(_translate("MainWidget", "Profile:"))
|
self.label.setText(_translate("MainWidget", "Profile:"))
|
||||||
self.addBut.setText(_translate("MainWidget", "Add"))
|
self.addBut.setText(_translate("MainWidget", "Add"))
|
||||||
self.editBut.setText(_translate("MainWidget", "Edit"))
|
self.editBut.setText(_translate("MainWidget", "Edit"))
|
||||||
self.copyBut.setToolTip(_translate("MainWidget", "Copy currently selected profile"))
|
|
||||||
self.copyBut.setText(_translate("MainWidget", "Copy"))
|
|
||||||
self.removeBut.setText(_translate("MainWidget", "Remove"))
|
self.removeBut.setText(_translate("MainWidget", "Remove"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
MainWidget = QtWidgets.QWidget()
|
||||||
|
ui = Ui_MainWidget()
|
||||||
|
ui.setupUi(MainWidget)
|
||||||
|
MainWidget.show()
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Form implementation generated from reading ui file 'soundactioneditwnd.ui'
|
|
||||||
#
|
|
||||||
# Created by: PyQt5 UI code generator 5.14.2
|
|
||||||
#
|
|
||||||
# WARNING! All changes made in this file will be lost!
|
|
||||||
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
||||||
|
|
||||||
|
|
||||||
class Ui_SoundSelect(object):
|
|
||||||
def setupUi(self, SoundSelect):
|
|
||||||
SoundSelect.setObjectName("SoundSelect")
|
|
||||||
SoundSelect.resize(1119, 362)
|
|
||||||
SoundSelect.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.labelVoicepacks = QtWidgets.QLabel(SoundSelect)
|
|
||||||
self.labelVoicepacks.setGeometry(QtCore.QRect(10, 30, 111, 17))
|
|
||||||
self.labelVoicepacks.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.labelVoicepacks.setObjectName("labelVoicepacks")
|
|
||||||
self.labelCategories = QtWidgets.QLabel(SoundSelect)
|
|
||||||
self.labelCategories.setGeometry(QtCore.QRect(160, 30, 111, 17))
|
|
||||||
self.labelCategories.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.labelCategories.setObjectName("labelCategories")
|
|
||||||
self.listFiles = QtWidgets.QListView(SoundSelect)
|
|
||||||
self.listFiles.setGeometry(QtCore.QRect(660, 90, 451, 231))
|
|
||||||
self.listFiles.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.listFiles.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
|
||||||
self.listFiles.setProperty("showDropIndicator", False)
|
|
||||||
self.listFiles.setAlternatingRowColors(True)
|
|
||||||
self.listFiles.setObjectName("listFiles")
|
|
||||||
self.listCategories = QtWidgets.QListView(SoundSelect)
|
|
||||||
self.listCategories.setEnabled(True)
|
|
||||||
self.listCategories.setGeometry(QtCore.QRect(160, 90, 491, 231))
|
|
||||||
self.listCategories.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.listCategories.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
|
||||||
self.listCategories.setProperty("showDropIndicator", False)
|
|
||||||
self.listCategories.setAlternatingRowColors(True)
|
|
||||||
self.listCategories.setObjectName("listCategories")
|
|
||||||
self.listVoicepacks = QtWidgets.QListView(SoundSelect)
|
|
||||||
self.listVoicepacks.setGeometry(QtCore.QRect(10, 50, 141, 271))
|
|
||||||
self.listVoicepacks.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.listVoicepacks.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
|
||||||
self.listVoicepacks.setProperty("showDropIndicator", False)
|
|
||||||
self.listVoicepacks.setAlternatingRowColors(True)
|
|
||||||
self.listVoicepacks.setObjectName("listVoicepacks")
|
|
||||||
self.labelFiles = QtWidgets.QLabel(SoundSelect)
|
|
||||||
self.labelFiles.setGeometry(QtCore.QRect(660, 30, 111, 17))
|
|
||||||
self.labelFiles.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.labelFiles.setObjectName("labelFiles")
|
|
||||||
self.buttonOkay = QtWidgets.QPushButton(SoundSelect)
|
|
||||||
self.buttonOkay.setEnabled(True)
|
|
||||||
self.buttonOkay.setGeometry(QtCore.QRect(940, 330, 80, 25))
|
|
||||||
self.buttonOkay.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.buttonOkay.setObjectName("buttonOkay")
|
|
||||||
self.buttonCancel = QtWidgets.QPushButton(SoundSelect)
|
|
||||||
self.buttonCancel.setGeometry(QtCore.QRect(1030, 330, 80, 25))
|
|
||||||
self.buttonCancel.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
|
||||||
self.buttonCancel.setObjectName("buttonCancel")
|
|
||||||
self.filterCategories = QtWidgets.QPlainTextEdit(SoundSelect)
|
|
||||||
self.filterCategories.setGeometry(QtCore.QRect(160, 50, 491, 31))
|
|
||||||
self.filterCategories.setObjectName("filterCategories")
|
|
||||||
self.filterFiles = QtWidgets.QPlainTextEdit(SoundSelect)
|
|
||||||
self.filterFiles.setGeometry(QtCore.QRect(660, 50, 451, 31))
|
|
||||||
self.filterFiles.setPlainText("")
|
|
||||||
self.filterFiles.setObjectName("filterFiles")
|
|
||||||
self.buttonPlaySound = QtWidgets.QPushButton(SoundSelect)
|
|
||||||
self.buttonPlaySound.setGeometry(QtCore.QRect(410, 330, 80, 25))
|
|
||||||
self.buttonPlaySound.setToolTip("")
|
|
||||||
self.buttonPlaySound.setObjectName("buttonPlaySound")
|
|
||||||
self.buttonStopSound = QtWidgets.QPushButton(SoundSelect)
|
|
||||||
self.buttonStopSound.setGeometry(QtCore.QRect(500, 330, 80, 25))
|
|
||||||
self.buttonStopSound.setToolTip("")
|
|
||||||
self.buttonStopSound.setObjectName("buttonStopSound")
|
|
||||||
|
|
||||||
self.retranslateUi(SoundSelect)
|
|
||||||
QtCore.QMetaObject.connectSlotsByName(SoundSelect)
|
|
||||||
|
|
||||||
def retranslateUi(self, SoundSelect):
|
|
||||||
_translate = QtCore.QCoreApplication.translate
|
|
||||||
SoundSelect.setWindowTitle(_translate("SoundSelect", "Sound selection"))
|
|
||||||
self.labelVoicepacks.setText(_translate("SoundSelect", "VoicePacks:"))
|
|
||||||
self.labelCategories.setText(_translate("SoundSelect", "Categories:"))
|
|
||||||
self.labelFiles.setText(_translate("SoundSelect", "Voice files:"))
|
|
||||||
self.buttonOkay.setText(_translate("SoundSelect", "Ok"))
|
|
||||||
self.buttonCancel.setText(_translate("SoundSelect", "Cancel"))
|
|
||||||
self.filterCategories.setToolTip(_translate("SoundSelect", "Filter categories"))
|
|
||||||
self.filterCategories.setStatusTip(_translate("SoundSelect", "Filter"))
|
|
||||||
self.filterFiles.setToolTip(_translate("SoundSelect", "Filter voice files"))
|
|
||||||
self.filterFiles.setStatusTip(_translate("SoundSelect", "Filter"))
|
|
||||||
self.buttonPlaySound.setText(_translate("SoundSelect", "Play Sound"))
|
|
||||||
self.buttonStopSound.setText(_translate("SoundSelect", "Stop Sound"))
|
|
Loading…
Reference in New Issue
Block a user