Add base version of run-transcribe.py

This commit is contained in:
dimitrievgs 2026-04-28 13:27:07 +03:00
parent 8f8e07ceae
commit 624923ef5f
11 changed files with 272 additions and 7 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ __pycache__
*.db *.db
*.db-journal *.db-journal
build/

17
.vscode/launch.json vendored
View File

@ -13,8 +13,19 @@
"FLASK_ENV": "development", "FLASK_ENV": "development",
"FLASK_DEBUG": "1" "FLASK_DEBUG": "1"
}, },
"justMyCode": true "justMyCode": true,
"console": "integratedTerminal"
},
{
"name": "Python: VK Transcribe",
"type": "debugpy",
"request": "launch",
"python": "${workspaceFolder}/env/Scripts/python.exe",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/run-transcribe.py",
"justMyCode": true,
"console": "integratedTerminal",
"args": []
} }
], ]
"console": "integratedTerminal"
} }

View File

@ -19,7 +19,7 @@ CORS(
# ----------------------------------------------- WebSocket Initialization --------------------------------------------------------------- # ----------------------------------------------- WebSocket Initialization ---------------------------------------------------------------
socketio = SocketIO( socketio = SocketIO(
api, cors_allowed_origins="*") # В production настроить CORS строже api, cors_allowed_origins="*") # В production настроить CORS строже # , async_mode='eventlet' для компиляции в exe
graph_history_manager.set_socketio(socketio) graph_history_manager.set_socketio(socketio)

View File

@ -1,3 +1,6 @@
# -*- coding: utf-8 -*-
# отключаем 40-ка строчное ограничение для этого файла # отключаем 40-ка строчное ограничение для этого файла
# ----------------------------------------------- Imports ------------------------------------------------------------ # ----------------------------------------------- Imports ------------------------------------------------------------
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional

View File

@ -54,6 +54,16 @@ MODELS: Dict[str, Dict[str, Any]] = {
"stream": True, "stream": True,
"capabilities": ["vision"], "capabilities": ["vision"],
}, },
"gemini-2.5-flash-openrouter": {
"name": "gemini-2.5-flash-openrouter",
"provider": "openai", # Изменено на "openai"
"model_name": "gemini-2.5-flash", # Добавлено имя модели для LangChain
"apiBase":
"https://openrouter.ai", # "generativelanguage.googleapis.com", # "https://render-service-gsu7.onrender.com/g/v1beta",
"apiKey": "AIzaSyDpueKFWVqknVKlQn6TdasLmJ2lvAUiBik",
"stream": True,
"capabilities": ["vision"],
},
"gemini-2.5-flash-bh": { "gemini-2.5-flash-bh": {
"name": "gemini-2.5-flash", "name": "gemini-2.5-flash",
"provider": "openai", # Изменено на "openai" "provider": "openai", # Изменено на "openai"
@ -64,6 +74,26 @@ MODELS: Dict[str, Dict[str, Any]] = {
"stream": True, "stream": True,
"capabilities": ["vision"], "capabilities": ["vision"],
}, },
"gemini-3-flash-preview-bh": {
"name": "gemini-3-flash-preview",
"provider": "openai", # Изменено на "openai"
"model_name": "gemini-3-flash-preview", # Добавлено имя модели для LangChain
"apiBase": "https://bothub.chat/api/v2/openai/v1",
"apiKey":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQ3MTMwYTk0LTJiNmMtNDgzZi04Y2NiLWM5MjgwNzlmNDFlZSIsImlzRGV2ZWxvcGVyIjp0cnVlLCJpYXQiOjE3NTQ0NzYwMDcsImV4cCI6MjA3MDA1MjAwN30._64_DXNZatdd4-4VbXp8dOXdDhIlNWMQt9A_Nn6mu3g",
"stream": True,
"capabilities": ["vision"],
},
"gemini-3.1-pro-preview-bh": {
"name": "gemini-3.1-pro-preview",
"provider": "openai", # Изменено на "openai"
"model_name": "gemini-3.1-pro-preview", # Добавлено имя модели для LangChain
"apiBase": "https://bothub.chat/api/v2/openai/v1",
"apiKey":
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQ3MTMwYTk0LTJiNmMtNDgzZi04Y2NiLWM5MjgwNzlmNDFlZSIsImlzRGV2ZWxvcGVyIjp0cnVlLCJpYXQiOjE3NTQ0NzYwMDcsImV4cCI6MjA3MDA1MjAwN30._64_DXNZatdd4-4VbXp8dOXdDhIlNWMQt9A_Nn6mu3g",
"stream": True,
"capabilities": ["vision"],
},
"claude-4.5-sonnet-bh": { "claude-4.5-sonnet-bh": {
"name": "claude-4.5-sonnet-bh", "name": "claude-4.5-sonnet-bh",
"provider": "openai", # Изменено на "openai" "provider": "openai", # Изменено на "openai"

BIN
geckodriver.exe Normal file

Binary file not shown.

65
obfuscate.txt Normal file
View File

@ -0,0 +1,65 @@
Для создания исполняемого файла (.exe), который запускается без установки, с обфусцированным кодом Python лучше всего использовать связку PyArmor + PyInstaller:
1. Обфускация с PyArmor:
- PyArmor шифрует и обфусцирует исходный Python-код, усложняя его анализ и обратную разработку.
- Используется командой:
```
pyarmor gen -O dist --recursive путь/к/вашему/проекту
```
или
```
pyarmor gen -O dist --recursive .
```
- Это создаст обфусцированные версии скриптов с сохранением структуры.
2. Упаковка в exe с PyInstaller:
- После обфускации используйте PyInstaller, чтобы упаковать проект в единый exe-файл с параметром `--onefile`:
```
pyinstaller --onefile --noconsole ваш_главный_скрипт.py
```
? python -m PyInstaller --onefile --noconsole run.py
? pyinstaller --onefile --noconsole --paths=./app run.py
? pyinstaller --onefile --hidden-import=eventlet.green --hidden-import=eventlet.green.thread --hidden-import=engineio.async_eventlet --hidden-import=engineio.async_drivers.eventlet --noconsole --paths=./app run.py
- Если нужно, добавляйте параметры `--add-data` для ресурсов.
3. Итог:
- Итоговый .exe-файл будет содержать интерпретатор Python, все зависимости и обфусцированный код.
- Запуск exe не требует предварительной установки Python или зависимостей.
- Такой файл удобно выкладывать в релизы GitHub и распространять пользователям.
4. Важные советы:
- Всегда тестируйте exe на чистой системе.
- PyArmor даёт хорошую защиту, но не абсолютную.
- Для повышения безопасности используйте активацию лицензий PyArmor (опционно).
- Настройте spec-файл PyInstaller, чтобы включить все необходимые модули и ресурсы.
Пример типичного workflow:
```bash
# Обфускация
pyarmor obfuscate --recursive your_project/
# Переход в обфусцированную папку
cd dist/your_project
# Создание exe
pyinstaller --onefile --noconsole main.py
```
Тогда в папке `dist` появится самодостаточный исполняемый файл.
Таким образом, PyArmor + PyInstaller позволяет создать exe с обфусцированным кодом, который не требует установки Python и готов к распространению через GitHub релизы.[1][7][9]
[1](https://pressanybutton.ru/post/poleznye-instrumenty/python-proektav-exe-fajl-i-obfuskaciya-koda/)
[2](https://www.youtube.com/watch?v=15ho_Zg_Kq4)
[3](https://www.youtube.com/watch?v=HtiYdCk0_J8)
[4](https://www.reddit.com/r/learnpython/comments/144tdhy/obfuscating_python_script/)
[5](https://stackoverflow.com/questions/58334936/pack-a-pyarmor-obfuscated-script-to-exe)
[6](https://qna.habr.com/q/1055278)
[7](https://pyarmor.readthedocs.io/en/v5.7.0/pack-obfuscated-scripts.html)
[8](https://github.com/dashingsoft/pyarmor)
[9](https://www.cyberforum.ru/python-ide/thread3151287.html)
[10](https://www.reddit.com/r/learnpython/comments/1bdsnce/which_one_is_best_for_code_obfuscation/)

View File

@ -11,3 +11,5 @@ flask_cors==6.0.1
flask_socketio flask_socketio
mistralai==1.9.10 mistralai==1.9.10
selenium

113
run-transcribe.py Normal file
View File

@ -0,0 +1,113 @@
import os
import time
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.common.by import By
# --- НАСТРОЙКИ ---
OBSIDIAN_PATH = r"D:\Work\Software_Development\Projects\ttrpg-obsidian-vault\scripts\transcrib\VK_Call_Notes.md"
CHECK_INTERVAL = 0.5 # Частота проверки текста
RECONNECT_INTERVAL = 6.0 # Частота поиска вкладки
def append_to_obsidian(text, new_line=True):
"""
Записывает текст.
new_line=True: создает новую строку с таймстампом.
new_line=False: дописывает в конец существующей строки.
"""
try:
with open(OBSIDIAN_PATH, "a", encoding="utf-8") as f:
if new_line:
timestamp = datetime.now().strftime("\n[%H:%M:%S] ")
f.write(f"{timestamp}{text}")
else:
f.write(f"{text}") # f.write(f" {text}")
print(f"Дописано: {text}")
except Exception as e:
print(f"Ошибка записи: {e}")
def find_vk_tab(driver):
"""Пытается найти и переключиться на вкладку ВК."""
try:
for handle in driver.window_handles:
driver.switch_to.window(handle)
if "vk.com" in driver.current_url or "Call" in driver.title:
return True
except:
pass
return False
def run_capture():
current_dir = os.path.dirname(os.path.abspath(__file__))
driver_path = os.path.join(current_dir, "geckodriver.exe")
options = Options()
options.add_argument("--debugger-address 127.0.0.1:9222")
service = Service(executable_path=driver_path)
try:
driver = webdriver.Firefox(service=service, options=options)
print("--- Соединение с браузером установлено ---")
except Exception as e:
print(f"Критическая ошибка: {e}")
return
last_text = ""
active_tab_found = False
try:
while True:
# 1. Если вкладка не найдена — ищем её
if not active_tab_found:
if find_vk_tab(driver):
print(f"Нашел звонок: {driver.title}. Начинаю слежку...")
active_tab_found = True
else:
print("Жду открытия вкладки со звонком ВК...")
time.sleep(RECONNECT_INTERVAL)
continue
# 2. Попытка прочитать текст
try:
element = driver.find_element(By.CLASS_NAME, "CallsSubtitles__content")
current_full_text = element.text.strip()
if current_full_text:
# Случай 1: Это продолжение текущей фразы
if last_full_text and current_full_text.startswith(last_full_text):
# Берем только то, чего еще не было
added_part = current_full_text[len(last_full_text):] #.strip()
if added_part:
append_to_obsidian(added_part, new_line=False)
last_full_text = current_full_text
# Случай 2: Это абсолютно новая фраза (предыдущая исчезла)
elif current_full_text != last_full_text:
append_to_obsidian(current_full_text, new_line=True)
last_full_text = current_full_text
else:
# В элементе пусто — сбрасываем кэш, чтобы следующая фраза шла с новой строки
last_full_text = ""
time.sleep(CHECK_INTERVAL)
except Exception:
# Если элемент потерян, сбрасываем состояние
last_full_text = ""
try:
_ = driver.current_url
except:
print("Вкладка потеряна. Ищу...")
active_tab_found = False
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
print("\nСкрипт остановлен.")
finally:
print("Сессия завершена.")
if __name__ == "__main__":
run_capture()

1
run.py
View File

@ -10,6 +10,7 @@ logger = logging.getLogger(__name__)
# ----------------------------------------------- Main ------------------------------------------------------------ # ----------------------------------------------- Main ------------------------------------------------------------
if __name__ == "__main__": if __name__ == "__main__":
# Добавляем папку app в Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
from app.api import api from app.api import api
from app.heartbeat_monitor import HeartbeatMonitor from app.heartbeat_monitor import HeartbeatMonitor

38
run.spec Normal file
View File

@ -0,0 +1,38 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['run.py'],
pathex=['./app'],
binaries=[],
datas=[],
hiddenimports=['eventlet.green', 'eventlet.green.thread', 'engineio.async_eventlet', 'engineio.async_drivers.eventlet'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='run',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)