#\ SundPood version 0201 /# import os import sys import json from time import time import pygame as pg import sounddevice as sd from pynput.keyboard import Listener from cryptography.fernet import Fernet from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtCore import QFile, QTextStream from PyQt5.QtWidgets import QApplication from data import ui_preferences from data import ui_hotkeys from data import ui_sundpood from data import ui_overlay from data import keys import themes import key ###! UI !### class OverlayUi(QtWidgets.QMainWindow, ui_overlay.Ui_MainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) self.setGeometry(QtCore.QRect(0, 0, 250, 20)) 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.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setupUi(self) def mousePressEvent(self, event): self.offset = event.pos() def mouseMoveEvent(self, event): x = event.globalX() y = event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x-x_w, y-y_w) def closeEvent(self, event): save() class HotkeysUi(QtWidgets.QMainWindow, ui_hotkeys.Ui_MainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setupUi(self) def mousePressEvent(self, event): self.offset = event.pos() def mouseMoveEvent(self, event): x = event.globalX() y = event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x-x_w, y-y_w) class MainUi(QtWidgets.QMainWindow, ui_sundpood.Ui_MainWindow): def __init__(self, parent=None): super().__init__(parent) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setupUi(self) self.volume_slider.valueChanged[int].connect(change_volume) def mousePressEvent(self, event): self.offset = event.pos() def mouseMoveEvent(self, event): try: x = event.globalX() y = event.globalY() x_w = self.offset.x() y_w = self.offset.y() self.move(x-x_w, y-y_w) except AttributeError: pass def keyPressEvent(self, e): if e.key() == QtCore.Qt.Key_F1: pref.close() self.hide() over.show() def closeEvent(self, event): save() if os.path.exists('.play'): os.remove('.play') hotk.close() 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 toggle_stylesheet(path): ''' Toggle the stylesheet to use the desired path in the Qt resource system (prefixed by `:/`) or generically (a path to a file on system). :path: A full path to a resource or file on system ''' # get the QApplication instance, or crash if not set app = QApplication.instance() if app is None: raise RuntimeError("No Qt Application found.") path = os.path.join('themes', path) file = QFile(path) file.open(QFile.ReadOnly | QFile.Text) stream = QTextStream(file) theme = stream.readAll() pref.setStyleSheet(theme) win.setStyleSheet(theme) hotk.setStyleSheet(theme) def find_device(): ''' Ищем устройство device и возвращаем его индекс ''' list_ = list(sd.query_devices()) # Список устройств вывода звука device = ["CABLE Input (VB-Audio Virtual Cable)", "VoiceMeeter Input (VB-Audio VoiceMeeter VAIO)"]# Имя искомого устройства 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_: for d in device: if d in i['name']: return i['name'] msg.exec_() exit() def get_files(dir_, config): ''' Сбор всех аудифайлов dir_ - Путь к папке со звуками config - Имя файла настроек ''' 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') if os.path.exists(dir_): if len(os.listdir(dir_)) == 0: msg.exec_() sounds = [] # Все аудио файлы sounds_list = [f'{dir_}\\'] # Начальная категория for i in os.listdir(dir_): if os.path.isfile(os.path.join(dir_, i)): name = os.path.join(dir_, i) if name == None: sounds_list.append(i) elif os.path.splitext(i)[1] in ['.mp3', '.m4a', '.wav']: sounds_list.append(i) else: sounds_list_cat = [os.path.join(dir_, i)] for x in os.listdir(os.path.join(dir_, i)): if os.path.isfile(os.path.join(dir_, i, x)): if name == None: sounds_list_cat.append(x) elif os.path.splitext(x)[1] in ['.mp3', '.m4a', '.wav']: sounds_list_cat.append(x) sounds.append(sounds_list_cat) sounds.append(sounds_list) if os.path.exists(config): hotkeys = jsonread(config)['hotkeys'] theme = jsonread(config)['Theme'] KEYS_CMD = jsonread(config)['KEYS_CMD'] sounds_list = { 'hotkeys':hotkeys, 'sounds':sounds, 'Theme':theme, 'KEYS_CMD':KEYS_CMD} else: sounds_list = { 'hotkeys':{}, 'sounds':sounds, 'Theme':'None', 'KEYS_CMD':{ 'select_move_up' :' ',# вверх 'select_move_down' :' ',# вниз 'select_move_left' :' ',# влево 'select_move_right' :' ',# вправо 'play_sound' :' ',# Играть 'stop_sound' :' ',# Остановить }} jsonwrite(config, sounds_list) else: sounds_list = { 'hotkeys':{}, 'sounds':'', 'Theme':'None', 'KEYS_CMD':{ 'select_move_up' :' ',# вверх 'select_move_down' :' ',# вниз 'select_move_left' :' ',# влево 'select_move_right' :' ',# вправо 'play_sound' :' ',# Играть 'stop_sound' :' ',# Остановить }} jsonwrite(config, sounds_list) os.mkdir(dir_) msg.exec_() def change_volume(value): pg.mixer.music.set_volume(value/100) def play_sound(*argv): if False in argv: try: sound = os.path.join(win.soundList.item(0).text(), win.soundList.currentItem().text()) except AttributeError: return False else: sound = argv[0] try: pg.mixer.music.load(sound) pg.mixer.music.play() pg.event.wait() except RuntimeError: pass def cat_select(cat): win.soundList.clear() for i in menu: if 'sound' + cat == i[0]: win.soundList.addItems(i) def hotkey_remap(): ''' Переназначение хоткея btn - индекс кнопки хоткея в списке HOTKEYS ''' def check(key): key = str(key).replace("'",'') try: sound = os.path.join(win.soundList.item(0).text(), win.soundList.currentItem().text()) except AttributeError: win.hkset.setEnabled(True) return False if key not in keys.forbidden: hotkeys.update({key:sound}) elif key == 'Key.backspace': del hotkeys[find_key(hotkeys, sound)] save() hotk.hotkeyList.clear() for i in sound_get_dict['sounds']: for x in i: x = os.path.join(i[0], x) if x in hotkeys.values(): hotk.hotkeyList.addItem(f'{find_key(hotkeys, x)}\t:{x}') win.hkset.setEnabled(True) return False win.hkset.setEnabled(False) hotkey_remap_Listener = Listener( on_release=check) hotkey_remap_Listener.start() def hotkey_delete(): key = hotk.hotkeyList.currentItem().text().split(':')[0].replace('\t', '') hotkeys.pop(key) save() hotk.hotkeyList.clear() for i in sound_get_dict['sounds']: for x in i: x = os.path.join(i[0], x) if x in hotkeys.values(): hotk.hotkeyList.addItem(f'{find_key(hotkeys, x)}\t:{x}') 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: KEYS_CMD.update({find_key(COMMAND_DICT, func_) : key}) btn.setText(keys.dict_[key]) elif key == 'Key.backspace': KEYS_CMD.update({find_key(COMMAND_DICT, func_) : ' '}) btn.setText(' ') for i in PREF_BTN: i.setEnabled(True) save() return False for i in PREF_BTN: i.setEnabled(False) hotkey_remap_Listener = Listener( on_release=check) hotkey_remap_Listener.start() def find_key(dict, val): ''' Поиск ключа в словаре dict по значению val dict - словарь val - значение ''' return next((key for key, value in dict.items() if value == val), None) def check_update(): def decrypt(filename, key): f = Fernet(key) with open(filename, 'rb') as file: encrypted_data = file.read() decrypted_data = f.decrypt(encrypted_data) return decrypted_data.decode('utf-8') file_ = decrypt(os.path.join('data', 'sundpood-runtime.sr') , key.KEY) version = '' for i in file_: if i != '/': version += i else: break print(VERSION) return version.split(' ')[3] def save(): ''' Сохранение настроек оверлея и глобальных хоткеев ''' sounds = jsonread(config)['sounds']# Все аудиофайлы KEYS_JSON = {} # Настроенные клавиши for i in KEYS_CMD.keys(): KEYS_JSON.setdefault(COMMAND_DICT[i], KEYS_CMD[i]) try: theme = pref.themesList.currentItem().text() except AttributeError: theme = jsonread(config)['Theme'] sounds_list = {'sounds':sounds, 'hotkeys':hotkeys, 'Theme':theme, 'KEYS_CMD':KEYS_JSON} jsonwrite(config, sounds_list) ###! 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 = str(key).replace("'",'') # Преобразование кода в строку key_n = '' # Переведенное значение клавиши try: key_n = keys.dict_[key] except KeyError: pass if key in hotkeys.keys(): play_sound(hotkeys[key]) elif key in KEYS_CMD.values(): find_key(KEYS_CMD, key)() def main(): key_check_Listener = Listener( on_release=key_check) key_check_Listener.start() win.exit_button.clicked.connect(win.close) win.min_button.clicked.connect(win.showMinimized) pref.exit_button.clicked.connect(pref.close) pref.min_button.clicked.connect(pref.showMinimized) hotk.exit_button.clicked.connect(hotk.close) hotk.min_button.clicked.connect(hotk.showMinimized) win.hkset.clicked.connect(hotkey_remap) win.pref_button.clicked.connect(pref.show) win.hotkeys_button.clicked.connect(hotk.show) win.catList.currentTextChanged.connect(cat_select) win.stop_button.clicked.connect(lambda: sd.stop()) win.play_button.clicked.connect(play_sound) 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')) pref.themesList.currentTextChanged.connect(toggle_stylesheet) pref.update_button.clicked.connect(check_update) hotk.delete_button.clicked.connect(hotkey_delete) win.show() if __name__ == '__main__': ### Создание окна ### app = QApplication([]) # Приложение over = OverlayUi() # Окно оверлея pref = PreferencesUi() # Окно настроек hotk = HotkeysUi() # Окно хоткеев win = MainUi() # Основное окно 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,] ### Поиск устроства ввода ### pg.mixer.pre_init(44100, -16, 2, 2048) # setup mixer to avoid sound lag pg.mixer.init(devicename=find_device()) # Установка устройства вывода по умолчанию ### Глобальные переменные ### VERSION = 102 dir_ = 'sound' config = 'settings.json' get_files(dir_, config) # Сбор всех аудиофайлов sound_get_dict = jsonread(config) # Загрузка данных hotkeys = sound_get_dict['hotkeys'] # Загрузка словаря хоткеев theme = sound_get_dict['Theme'] # Загрузка темы menu = sound_get_dict['sounds'] # Загрузка оверлейного меню select = [0, 0] # Установка курсора оверлея в нулевую позицию if theme != 'None': toggle_stylesheet(theme) pref.themesList.addItems(os.listdir('themes')) for i in sound_get_dict['sounds']: win.catList.addItem(i[0].replace('sound', '')) for x in i: x = os.path.join(i[0], x) if x in hotkeys.values(): hotk.hotkeyList.addItem(f'{find_key(hotkeys, x)}\t: {x}') 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(os.path.join(menu[select[0]][0], menu[select[0]][select[1]])):'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 main() sys.exit(app.exec())