1010from PIL import Image , ImageTk
1111import sounddevice as sd
1212import sys
13- import logging
14- import logging .handlers
15- from datetime import datetime
1613import psutil
14+ import socket
1715
1816# Определение базовой директории
1917if getattr (sys , 'frozen' , False ):
2018 BASE_DIR = os .path .dirname (sys .executable )
2119else :
2220 BASE_DIR = os .path .dirname (os .path .abspath (__file__ ))
2321
24- # Создание папки для логов
25- LOGS_DIR = os .path .join (BASE_DIR , "logs" )
26- os .makedirs (LOGS_DIR , exist_ok = True )
27-
28- # Настройка логирования для main
29- def setup_main_logging ():
30- logger = logging .getLogger ('main' )
31- logger .setLevel (logging .DEBUG )
32-
33- # Форматирование
34- formatter = logging .Formatter ('%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
35-
36- # Файловый обработчик с ротацией
37- log_file = os .path .join (LOGS_DIR , 'main.log' )
38- file_handler = logging .handlers .RotatingFileHandler (
39- log_file , maxBytes = 1048576 , backupCount = 5 # 1MB
40- )
41- file_handler .setFormatter (formatter )
42-
43- # Консольный обработчик
44- console_handler = logging .StreamHandler ()
45- console_handler .setFormatter (formatter )
46-
47- logger .addHandler (file_handler )
48- logger .addHandler (console_handler )
49-
50- return logger
51-
52- # Инициализация логгера
53- logger = setup_main_logging ()
22+ # Импортируем логирование из utils
23+ from utils import setup_logging
24+ logger = setup_logging ('main' )
5425
5526MODELS_DIR = os .path .join (BASE_DIR , "models" )
5627os .makedirs (MODELS_DIR , exist_ok = True )
@@ -93,6 +64,9 @@ def __init__(self, root):
9364 # Загрузка настроек
9465 self .settings = self .load_settings ()
9566
67+ # Загружаем порт из настроек (по умолчанию 6969)
68+ self .webserver_port = self .settings .get ('webserver_port' , 6969 )
69+
9670 # Инициализация компонентов
9771 self .renderer = Renderer (width = 700 , height = 700 , fps = 60 )
9872 self .audio = AudioProcessor (callback = self .on_audio_level ,
@@ -216,6 +190,14 @@ def __init__(self, root):
216190 )
217191 self .link_btn .pack (fill = "x" , padx = 2 , pady = 2 )
218192
193+ # Кнопка изменения порта
194+ self .port_btn = ttk .Button (
195+ control_frame ,
196+ text = f"Порт: { self .webserver_port } " ,
197+ command = self .change_port
198+ )
199+ self .port_btn .pack (fill = "x" , padx = 2 , pady = 2 )
200+
219201 # Настройки микрофона
220202 mic_frame = ttk .LabelFrame (settings_frame , text = "🎤 Микрофон" )
221203 mic_frame .pack (fill = "x" , pady = (0 , 3 ), padx = 3 )
@@ -476,6 +458,9 @@ def configure_scrollregion(event):
476458 self .renderer .set_noise_gate (self .noise_gate_threshold .get () if self .noise_gate_enabled .get () else 0.0 )
477459 self .renderer .set_idle (self .idle_enabled .get (), self .idle_timeout .get ())
478460
461+ # Создаём веб-сервер с нужным портом (но не запускаем)
462+ self .webserver = WebServer (self .renderer , port = self .webserver_port )
463+
479464 # Обновление слотов
480465 self .refresh_slot_buttons ()
481466
@@ -776,13 +761,121 @@ def open_web_link(self):
776761 """Открытие ссылки веб-сервера в браузере"""
777762 import webbrowser
778763 try :
779- url = "http://localhost:6969 /"
764+ url = f "http://localhost:{ self . webserver_port } /"
780765 webbrowser .open (url )
781766 logger .info (f"Opened web link: { url } " )
782767 except Exception as e :
783768 logger .error (f"Error opening web link: { e } " )
784769 messagebox .showerror ("Ошибка" , f"Не удалось открыть ссылку: { e } " )
785770
771+ def change_port (self ):
772+ """Изменение порта веб-сервера (из старой версии)"""
773+ if self .webserver and getattr (self .webserver , "is_running" , False ):
774+ messagebox .showwarning ("Веб-сервер запущен" ,
775+ "Пожалуйста, остановите веб-сервер перед изменением порта." )
776+ return
777+
778+ dialog = tk .Toplevel (self .root )
779+ dialog .title ("Изменить порт веб-сервера" )
780+ dialog .geometry ("300x150" )
781+ dialog .resizable (False , False )
782+ dialog .transient (self .root )
783+ dialog .grab_set ()
784+
785+ dialog .update_idletasks ()
786+ x = self .root .winfo_x () + (self .root .winfo_width () // 2 ) - (300 // 2 )
787+ y = self .root .winfo_y () + (self .root .winfo_height () // 2 ) - (150 // 2 )
788+ dialog .geometry (f"+{ x } +{ y } " )
789+
790+ ttk .Label (dialog , text = "Введите новый порт (1-65535):" ,
791+ font = ("Arial" , 10 )).pack (pady = (15 , 5 ))
792+
793+ port_frame = ttk .Frame (dialog )
794+ port_frame .pack (pady = 10 )
795+
796+ port_var = tk .StringVar (value = str (self .webserver_port ))
797+ port_entry = ttk .Entry (port_frame , textvariable = port_var , width = 10 ,
798+ font = ("Arial" , 12 ), justify = "center" )
799+ port_entry .pack (side = "left" , padx = 5 )
800+ port_entry .select_range (0 , tk .END )
801+ port_entry .focus_set ()
802+
803+ def is_port_available (port ):
804+ """Проверяет, свободен ли порт (не слушает ли его другой процесс)"""
805+ try :
806+ if port == self .webserver_port and not (self .webserver and self .webserver .is_running ):
807+ return True
808+
809+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
810+ sock .settimeout (0.5 )
811+ result = sock .connect_ex (('localhost' , port ))
812+ sock .close ()
813+ return result != 0
814+ except :
815+ return False
816+
817+ def apply_port ():
818+ try :
819+ new_port = int (port_var .get ())
820+ if new_port < 1 or new_port > 65535 :
821+ messagebox .showerror ("Ошибка" ,
822+ "Порт должен быть в диапазоне 1-65535" , parent = dialog )
823+ return
824+
825+ if new_port == self .webserver_port :
826+ dialog .destroy ()
827+ return
828+
829+ if not is_port_available (new_port ):
830+ # Пробуем подождать, вдруг порт освободится
831+ for attempt in range (3 ):
832+ if is_port_available (new_port ):
833+ break
834+ time .sleep (0.5 )
835+
836+ if not is_port_available (new_port ):
837+ messagebox .showerror ("Порт занят" ,
838+ f"Порт { new_port } уже занят другим процессом.\n Попробуйте другой порт." ,
839+ parent = dialog )
840+ return
841+
842+ old_port = self .webserver_port
843+ self .webserver_port = new_port
844+
845+ self .port_btn .config (text = f"Порт: { self .webserver_port } " )
846+
847+ # Пересоздаём веб-сервер с новым портом
848+ self .webserver = WebServer (self .renderer , port = self .webserver_port )
849+
850+ self .save_settings ()
851+
852+ self .show_temporary_message (
853+ "Порт изменен" ,
854+ f"Порт веб-сервера изменен: { old_port } → { self .webserver_port } "
855+ )
856+
857+ dialog .destroy ()
858+
859+ except ValueError :
860+ messagebox .showerror ("Ошибка" ,
861+ "Порт должен быть целым числом" , parent = dialog )
862+ except Exception as e :
863+ messagebox .showerror ("Ошибка" ,
864+ f"Не удалось изменить порт: { e } " , parent = dialog )
865+ logger .error (f"Error changing port: { e } " )
866+
867+ btn_frame = ttk .Frame (dialog )
868+ btn_frame .pack (pady = 15 )
869+
870+ ttk .Button (btn_frame , text = "Применить" ,
871+ command = apply_port ).pack (side = "left" , padx = 5 )
872+ ttk .Button (btn_frame , text = "Отмена" ,
873+ command = dialog .destroy ).pack (side = "left" , padx = 5 )
874+
875+ dialog .bind ("<Return>" , lambda e : apply_port ())
876+
877+ self .root .wait_window (dialog )
878+
786879 def _round_to_step (self , value , step ):
787880 """Округление значения до ближайшего шага"""
788881 return round (value / step ) * step
@@ -951,6 +1044,7 @@ def save_settings(self):
9511044 'idle_enabled' : self .idle_enabled .get (),
9521045 'idle_timeout' : self .idle_timeout .get (),
9531046 'current_slot' : self .current_slot ,
1047+ 'webserver_port' : self .webserver_port , # Сохраняем порт
9541048 'sections_state' : { # Сохраняем состояние раскрытия секций
9551049 'effects' : self .effects_expanded ,
9561050 'wave' : self .wave_expanded ,
@@ -1313,6 +1407,7 @@ def toggle_server(self):
13131407 self .webserver .stop ()
13141408 self .server_btn .config (text = "🌐 Запустить веб-сервер" )
13151409 self .link_btn .config (state = "disabled" )
1410+ self .port_btn .config (state = "normal" )
13161411 logger .info ("Web server stopped" )
13171412
13181413 # Останавливаем рендерер только если он был запущен для веб-сервера
@@ -1325,7 +1420,7 @@ def toggle_server(self):
13251420 logger .error (f"Error stopping renderer: { e } " )
13261421 else :
13271422 if not self .webserver :
1328- self .webserver = WebServer (self .renderer )
1423+ self .webserver = WebServer (self .renderer , port = self . webserver_port )
13291424 elif not self .webserver .is_running :
13301425 self .webserver .renderer = self .renderer
13311426
@@ -1364,6 +1459,7 @@ def toggle_server(self):
13641459 self .webserver .start ()
13651460 self .server_btn .config (text = "⏹️ Остановить веб-сервер" )
13661461 self .link_btn .config (state = "normal" )
1462+ self .port_btn .config (state = "disabled" )
13671463 logger .info ("Web server started" )
13681464 except Exception as e :
13691465 logger .error (f"Error starting web server: { e } " )
0 commit comments