Sundpood/main.py
2020-10-30 01:50:29 +03:00

508 lines
19 KiB
Python

import os
import sys
import json
import threading
import soundfile as sf
import sounddevice as sd
from pynput.keyboard import Listener
from PyQt5 import QtWidgets, QtGui, QtCore
import ui_preferences
import ui_sundpood
import ui_overlay
import keys
###! UI !###
class OverlayUi(QtWidgets.QMainWindow, ui_overlay.Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_F1:
self.hide()
win.show()
class PreferencesUi(QtWidgets.QMainWindow, ui_preferences.Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
class MainUi(QtWidgets.QMainWindow, ui_sundpood.Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
def keyPressEvent(self, e):
if e.key() == QtCore.Qt.Key_F1:
pref.close()
self.hide()
over.show()
def closeEvent(self, event):
pref.close()
###! JSON !###
def jsonread(file):
'''
Чтение JSON файла file
file - имя JSON файла, может содержать полный или относительный путь
'''
with open(file, "r", encoding='utf-8') as read_file:
data = json.load(read_file)
return data
def jsonwrite(file, data):
'''
Запись в JSON файл file данные data
file - имя JSON файла, может содержать полный или относительный путь
data - данные, может быть словарем/списком/кортежем/строкой/числом
'''
with open(file, 'w', encoding='utf-8') as write_file:
write_file.write(json.dumps(data))
###! FUNCTIONS !###
def find_device():
'''
Ищем устройство device и возвращаем его индекс
'''
list_ = list(sd.query_devices()) # Список устройств вывода звука
device = 'VoiceMeeter Input' # Имя искомого устройства
msg = QtWidgets.QMessageBox() # Окно ошибки (Устройство не найдено)
msg.setIcon(QtWidgets.QMessageBox.Critical)
msg.setText("You don't install VoiceMeeter")
msg.setInformativeText('install VoiceMetter from "redist" folder or download it from \nvb-audio.com/Voicemeeter')
msg.setWindowTitle('Error')
for i in list_:
if device in i['name']:
return i['name']
msg.exec_()
exit()
def sound_get():
'''
Сбор всех аудифайлов
'''
def check_format(name, format_):
'''
Проверка формата файла name
name - название файла, только название
format_ - нужный формат
'''
suf = '' # Суффикс
while name[-1] != '.':
suf += name[-1]
name = name[:-1]
suf = suf[::-1]
if type(format_) is list:
if suf in format_:
return True
else:
return False
elif type(format_) is str:
if suf == format_:
return True
else:
return False
def sound_convert(path, name, format_):
'''
Конвертация файла name в папке path в формат format_
по средством ffmpeg.exe
path - папка с файлом, может содержать полный или относительный путь
name - название файла, может только название файла
format_ - формат в который нужно конвертировать
'''
if os.path.exists('ffmpeg.exe'):
old_name = name
if check_format(name, ['mp3', 'm4a']):
while name[-1] != '.':
name = name[:-1]
name += format_
os.system(f'ffmpeg.exe -i "{os.path.join(path, old_name)}" "{os.path.join(path, name)}"')
os.remove(f'{os.path.join(path, old_name)}')
return name
msg = QtWidgets.QMessageBox() # Окно ошибки (Не найдены файлы в папке 'sound')
msg.setIcon(QtWidgets.QMessageBox.Critical)
msg.setText("You don't have any sounds in 'sound' folder")
msg.setInformativeText('download sound in .wav / .mp3 / .m4a format')
msg.setWindowTitle('Error')
ffmsg = QtWidgets.QMessageBox() # Окно ошибки (Не найден ffmpeg.exe)
ffmsg.setIcon(QtWidgets.QMessageBox.Warning)
ffmsg.setText("You don't have ffmpeg.exe in the program root folder")
ffmsg.setInformativeText('download ffmpeg from ffmpeg.org for converting sound files')
ffmsg.setWindowTitle('Error')
if os.path.exists('sound'):
if len(os.listdir('sound')) == 0:
msg.exec_()
menu = [] # Оверлейное меню
sounds_list = ['sound\\'] # Начальная категория
sounds = [] # Все аудиофайлы
for i in os.listdir('sound'):
if os.path.exists('ffmpeg.exe') or not ffmsg:
if os.path.isfile(os.path.join('sound', i)):
name = sound_convert('sound', i, 'wav')
if name == None:
sounds_list.append(i)
else:
sounds_list.append(name)
else:
sounds_list_cat = [os.path.join('sound', i)]
for x in os.listdir(os.path.join('sound', i)):
if os.path.isfile(os.path.join('sound', i, x)):
name = sound_convert(os.path.join('sound', i), x, 'wav')
if name == None:
sounds_list_cat.append(x)
else:
sounds_list_cat.append(name)
menu.append(sounds_list_cat)
else:
print('else')
try:
ffmsg.exec_()
ffmsg = False
except AttributeError:
pass
menu.append(sounds_list)
for i in os.listdir('sound'):
if os.path.isfile(os.path.join('sound', i)):
if check_format(i, 'wav'):
sounds.append(i)
if os.path.exists('settings.json'):
hotkeys = jsonread('settings.json')['hotkeys']
KEYS_CMD = jsonread('settings.json')['KEYS_CMD']
sounds_list = { 'sounds':sounds,
'hotkeys':hotkeys,
'menu':menu,
'KEYS_CMD':KEYS_CMD}
else:
sounds_list = { 'sounds':sounds,
'hotkeys':{'Push button':''},
'menu':menu,
'KEYS_CMD':{
'select_move_up' :' ',# вверх
'select_move_down' :' ',# вниз
'select_move_left' :' ',# влево
'select_move_right' :' ',# вправо
'play_sound' :' ',# Играть
'stop_sound' :' ',# Остановить
}}
for i in COMBOS:
i.addItems(sounds)
jsonwrite('settings.json', sounds_list)
else:
sounds_list = { 'sounds':'',
'hotkeys':{'Push button':''},
'menu':'',
'KEYS_CMD':{
'select_move_up' :' ',# вверх
'select_move_down' :' ',# вниз
'select_move_left' :' ',# влево
'select_move_right' :' ',# вправо
'play_sound' :' ',# Играть
'stop_sound' :' ',# Остановить
}}
jsonwrite('settings.json', sounds_list)
os.mkdir('sound')
msg.exec_()
def save():
'''
Сохранение настроек оверлея и глобальных хоткеев
'''
hotkeys = {} # Словарь хоткеев и аудиофайлов
sounds = jsonread('settings.json')['sounds']# Все аудиофайлы
KEYS_JSON = {} # Настроенные клавиши
for i in range(len(COMBOS)):
hotkeys.setdefault(HOTKEYS[i].text(), COMBOS[i].currentText())
for i in KEYS_CMD.keys():
KEYS_JSON.setdefault(COMMAND_DICT[i], KEYS_CMD[i])
sounds_list = {'sounds':sounds, 'hotkeys':hotkeys, 'menu':menu, 'KEYS_CMD':KEYS_JSON}
jsonwrite('settings.json', sounds_list)
def play_sound(index):
'''
Проигрывание звука
index - может быть индексом комбобокса с именем
или '' для проигрывания выбранного в оверлее имени
'''
try:
filename = COMBOS[index].currentText()
try:
data, fs = sf.read(os.path.join('sound', filename), dtype='float32')
sd.play(data, fs)
except:
pass
except:
filename = menu[select[0]][select[1]]
try:
data, fs = sf.read(os.path.join(menu[select[0]][0], filename), dtype='float32')
sd.play(data, fs)
except:
pass
def hotkey_remap(btn):
'''
Переназначение хоткея
btn - индекс кнопки хоткея в списке HOTKEYS
'''
def check(key):
'''
Проверка кнопки btn на не участие
в списке запрещенных клавиш keys.forbidden
если это клавиша 'Key.backspace' то стираем значение
key - pynput код клавиши
'''
button = HOTKEYS[btn]
key = str(key).replace("'",'')
if key not in keys.forbidden:
HOTKEYS_CMD[HOTKEYS_CMD.index(button.text())] = keys.dict_[key]
button.setText(keys.dict_[key])
elif key == 'Key.backspace':
HOTKEYS_CMD[HOTKEYS_CMD.index(button.text())] = ' '
button.setText(' ')
for i in HOTKEYS:
if i != HOTKEYS[btn]:
i.setEnabled(True)
return False
for i in HOTKEYS:
if i != HOTKEYS[btn]:
i.setEnabled(False)
hotkey_remap_Listener = Listener(
on_release=check)
hotkey_remap_Listener.start()
def pref_remap(btn, func_):
'''
Переназначение клавиши в окно Preference
btn - PyQt5 кнопка
func_ - строковое значени функции из словаря KEYS_CMD
'''
def check(key):
'''
Проверка кнопки btn на не участие
в списке запрещенных клавиш keys.forbidden
если это клавиша 'Key.backspace' то стираем значение
key - pynput код клавиши
'''
key = str(key).replace("'",'')
if key not in keys.forbidden:
func = find_key(COMMAND_DICT, func_)
KEYS_CMD.update({func : key})
btn.setText(keys.dict_[key])
elif key == 'Key.backspace':
func = find_key(COMMAND_DICT, func_)
KEYS_CMD.update({func : ' '})
btn.setText(' ')
for i in PREF_BTN:
i.setEnabled(True)
return False
for i in PREF_BTN:
i.setEnabled(False)
hotkey_remap_Listener = Listener(
on_release=check)
hotkey_remap_Listener.start()
save()
def find_key(dict, val):
'''
Поиск ключа в словаре dict по значению val
dict - словарь
val - значение
'''
return next(key for key, value in dict.items() if value == val)
###! CONTROL !###
def select_move(mode):
'''
Перемешение по оверлейному меню
mode - кортеж из двух цифр
(смещение по категории, смещение по списку)
'''
select[0] += mode[0] # Категории
select[1] += mode[1] # Файлы в категории
if select[0] > len(menu)-1 or select[0] < -len(menu)+1:
select[0] = 0
if select[1] > len(menu[select[0]])-1 or select[1] < -len(menu[select[0]])+1:
select[1] = 0
if mode[0] != 0:
select[1] = 0
over.label.setText(menu[select[0]][select[1]])
win.select_label.setText(menu[select[0]][select[1]])
def key_check(key):
'''
Проверка клавиши key на наличие записанных функций
key - pynput код клавиши
'''
key = str(key).replace("'",'') # Преобразование кода в строку
key_n = '' # Переведенное значение клавиши
try:
key_n = keys.dict_[key]
except KeyError:
pass
if key_n in HOTKEYS_CMD:
play_sound(HOTKEYS_CMD.index(key_n))
elif key in KEYS_CMD.values():
find_key(KEYS_CMD, key)()
def main():
'''
Открытие окна win, запуск слушателя клавиатуры,
подключение кнопок интерфейса к функциям
'''
win.show()
key_check_Listener = Listener(
on_release=key_check)
key_check_Listener.start()
win.save_button.clicked.connect(save)
win.actionpreferences.triggered.connect(pref.show)
win.hotkey_1.clicked.connect(lambda: hotkey_remap(0))
win.hotkey_2.clicked.connect(lambda: hotkey_remap(1))
win.hotkey_3.clicked.connect(lambda: hotkey_remap(2))
win.hotkey_4.clicked.connect(lambda: hotkey_remap(3))
win.hotkey_5.clicked.connect(lambda: hotkey_remap(4))
win.hotkey_6.clicked.connect(lambda: hotkey_remap(5))
win.hotkey_7.clicked.connect(lambda: hotkey_remap(6))
win.hotkey_8.clicked.connect(lambda: hotkey_remap(7))
win.hotkey_9.clicked.connect(lambda: hotkey_remap(8))
win.hotkey_10.clicked.connect(lambda: hotkey_remap(9))
win.hotkey_11.clicked.connect(lambda: hotkey_remap(10))
win.hotkey_12.clicked.connect(lambda: hotkey_remap(11))
pref.play_sound.clicked.connect(
lambda: pref_remap(pref.play_sound, 'play_sound'))
pref.stop_sound.clicked.connect(
lambda: pref_remap(pref.stop_sound, 'stop_sound'))
pref.select_move_up.clicked.connect(
lambda: pref_remap(pref.select_move_up, 'select_move_up'))
pref.select_move_down.clicked.connect(
lambda: pref_remap(pref.select_move_down, 'select_move_down'))
pref.select_move_left.clicked.connect(
lambda: pref_remap(pref.select_move_left, 'select_move_left'))
pref.select_move_right.clicked.connect(
lambda: pref_remap(pref.select_move_right, 'select_move_right'))
if __name__ == '__main__':
### Создание окна ###
app = QtWidgets.QApplication([]) # Приложение
over = OverlayUi() # Окно оверлея
pref = PreferencesUi() # Окно настроек
win = MainUi() # Основное окно
COMBOS = [ # Список комбобоксов в win
win.combo0,
win.combo1,
win.combo2,
win.combo3,
win.combo4,
win.combo5,
win.combo6,
win.combo7,
win.combo8,
win.combo9,
win.combo10,
win.combo11,]
HOTKEYS = [ # Список кнопок хоткеев в win
win.hotkey_1,
win.hotkey_2,
win.hotkey_3,
win.hotkey_4,
win.hotkey_5,
win.hotkey_6,
win.hotkey_7,
win.hotkey_8,
win.hotkey_9,
win.hotkey_10,
win.hotkey_11,
win.hotkey_12,]
PREF_BTN = [ # Список кнопок настроек клавиш в pref
pref.select_move_up,
pref.select_move_down,
pref.select_move_left,
pref.select_move_right,
pref.play_sound,
pref.stop_sound,]
### Поиск устроства ввода ###
sd.default.device = find_device() # Установка устройства вывода по умолчанию
sound_get() # Сбор всех аудиофайлов
### Глобальные переменные ###
sound_get_dict = jsonread('settings.json')# Загрузка данных
hotkeys = sound_get_dict['hotkeys'] # Загрузка словаря хоткеев
menu = sound_get_dict['menu'] # Загрузка оверлейного меню
select = [0, 0] # Установка курсора оверлея в нулевую позицию
COMMAND_DICT = { # Словарь функций к строковому значению
lambda: select_move((0, -1)):'select_move_up', # вверх
lambda: select_move((0, 1)) :'select_move_down', # вниз
lambda: select_move((-1, 0)):'select_move_left', # влево
lambda: select_move((1, 0)) :'select_move_right', # вправо
lambda: play_sound('') :'play_sound', # Играть
lambda: sd.stop() :'stop_sound', # Остановить
}
KEYS_JSON = sound_get_dict['KEYS_CMD']# Загрузка настроенных клавиш
KEYS_CMD = COMMAND_DICT.copy() # Настроенные клавиши
### Установка настроенных клавиш ###
for i in KEYS_CMD.keys():
KEYS_CMD.update({i:KEYS_JSON[COMMAND_DICT[i]]})
KEYS_JSON = None
combo = 0
for i in KEYS_CMD.values():
PREF_BTN[combo].setText(keys.dict_[i])
combo += 1
### Установка хоткеев ###
combo = 0
for i in hotkeys.items():
index = COMBOS[combo].findText(i[1])
COMBOS[combo].setCurrentIndex(index)
HOTKEYS[combo].setText(i[0])
combo += 1
combo = None
HOTKEYS_CMD = [ # Имена связанные с хоткеями
HOTKEYS[0].text(),
HOTKEYS[1].text(),
HOTKEYS[2].text(),
HOTKEYS[3].text(),
HOTKEYS[4].text(),
HOTKEYS[5].text(),
HOTKEYS[6].text(),
HOTKEYS[7].text(),
HOTKEYS[8].text(),
HOTKEYS[9].text(),
HOTKEYS[10].text(),
HOTKEYS[11].text(),]
main()
sys.exit(app.exec())