# Speakr - 定制版 > 基于 [murtaza-nasir/speakr](https://github.com/murtaza-nasir/speakr) 二次开发的语音转录与智能笔记平台。 ## 项目简介 本项目是一个自托管的 AI 语音转录和智能笔记平台,支持: - **语音录制与上传** - 浏览器直接录音或上传已有音频文件 - **AI 转录** - 高准确率语音转文字,支持说话人识别 - **交互式对话** - 对录音提问,获取 AI 回答 - **智能标签** - 支持带自定义 AI 提示词的标签 - **安全分享** - 生成安全链接分享录音 - **草稿功能** - 本地草稿保存与上传 - **声纹管理** - 自定义说话人声纹识别 - **标准词映射** - 自动纠正专业术语和人名 --- ## 项目结构 ``` speakr/ ├── app.py # 入口文件(启动脚本) ├── src/ │ ├── app.py # Flask 应用主逻辑(create_app 工厂模式) │ ├── config.py # 统一配置文件(所有环境变量集中管理) │ ├── logging_config.py # 日志配置模块(格式化、输出目标、日志级别) │ ├── flask_ext.py # Flask 扩展实例集合(LoginManager、Bcrypt、Limiter、CSRFProtect) │ ├── clients/ # 外部服务客户端层 │ │ ├── __init__.py # 统一导出所有客户端和配置 │ │ ├── database.py # 数据库客户端:db 实例 + 配置 + 初始化 │ │ ├── llm_client.py # LLM 客户端:OpenAI 客户端初始化 │ │ └── voiceprint_client.py # 声纹客户端:VoiceprintClient │ ├── api/ # API 路由层(Flask 蓝图) │ │ ├── __init__.py # 统一蓝图注册函数 register_blueprints() │ │ ├── template_filters.py # 模板过滤器注册(now、localdatetime) │ │ ├── auth.py # 认证路由(登录、注册、账户管理) │ │ ├── recording.py # 录音管理(CRUD、上传、转录、下载) │ │ ├── share.py # 分享功能 │ │ ├── tag.py # 标签管理 │ │ ├── speaker.py # 说话人管理 │ │ ├── admin.py # 管理后台 │ │ ├── search.py # 搜索和 Inquire │ │ ├── voiceprint.py # 声纹管理 │ │ ├── config.py # 系统配置管理 │ │ ├── hotword.py # 热词管理 │ │ ├── template.py # 转录模板 │ │ ├── event.py # 事件管理 │ │ └── main.py # 主页和杂项 │ ├── models/ # 数据模型层(每个模型独立文件) │ │ ├── __init__.py # 统一导出所有模型 │ │ ├── user.py # 用户模型 │ │ ├── speaker.py # 说话人模型 │ │ ├── recording.py # 录音模型 │ │ ├── tag.py # 标签模型 │ │ ├── recording_tag.py # 录音-标签关联模型 │ │ ├── event.py # 事件模型 │ │ ├── share.py # 分享模型 │ │ ├── transcript_chunk.py # 转录块模型 │ │ ├── transcript_template.py # 转录模板模型 │ │ ├── inquire_session.py # 询问会话模型 │ │ ├── voiceprint.py # 声纹模型 │ │ ├── system_setting.py # 系统设置模型 │ │ ├── system_config.py # 系统配置模型 │ │ ├── hotword.py # 热词模型 │ │ ├── draft_recording.py # 草稿录音模型 │ │ ├── draft_segment.py # 草稿片段模型 │ │ └── forms.py # 表单类(注册、登录) │ ├── services/ # 业务逻辑层 │ │ ├── __init__.py # 统一导出所有服务函数 │ │ ├── llm_service.py # LLM 服务 │ │ ├── transcription_service.py # 转录服务 │ │ ├── summary_service.py # 摘要服务 │ │ ├── embedding_service.py # 嵌入向量服务 │ │ ├── event_service.py # 事件服务 │ │ ├── document_service.py # 文档服务 │ │ ├── speaker_service.py # 说话人服务 │ │ ├── voiceprint_service.py # 声纹服务(业务逻辑) │ │ ├── auto_process_service.py # 自动处理服务 │ │ ├── auth_service.py # 认证服务 │ │ ├── sync_service.py # 远程数据同步服务 │ │ ├── audio_chunking.py # 音频分块服务 │ │ └── file_monitor.py # 文件监控服务 │ ├── utils/ # 工具函数层 │ │ ├── __init__.py │ │ ├── markdown.py # Markdown 转 HTML 工具 │ │ ├── datetime.py # 时区转换工具 │ │ ├── text_utils.py # 文本处理工具 │ │ ├── json_utils.py # JSON 处理工具 │ │ └── standard_word_mappings.py # 标准词映射工具 ├── templates/ # Jinja2 模板 ├── static/ # 静态资源(CSS/JS/图片) ├── instance/ # SQLite 数据库文件 ├── uploads/ # 上传的音频文件 └── .env # 环境变量配置 ``` --- ## 启动方式 ### 前置条件 - Python 环境(已安装 conda 环境 `speakr`) - Nginx 反向代理(必须启动) ### 启动步骤 ```bash # 1. 进入项目根目录(speakr 2 目录) cd "c:\Work\zdht\python-projects\speakr 2" # 2. 启动 Nginx 反向代理(必须先启动) # Nginx 负责将 https://localhost:8083/tool/speakr/ 请求转发到 Flask 后端 # ⚠️ 注意:不启动 Nginx 将无法访问应用 nginx # 或根据你的安装方式启动 nginx # 3. 激活 conda 环境并启动应用 conda activate speakr python .\speakr\app.py ``` Flask 后端默认监听 **5000 端口**,Nginx 负责将 `https://localhost:8083/tool/speakr/` 请求转发到 `http://localhost:5000`。 ### 访问地址 - 主页面:`https://localhost:8083/tool/speakr/` - 管理后台:`https://localhost:8083/tool/speakr/admin` - 账户设置:`https://localhost:8083/tool/speakr/account` ### 常见错误 | 问题 | 原因 | 解决 | |------|------|------| | 页面无法访问 | Nginx 未启动 | 启动 Nginx 反向代理 | | `ModuleNotFoundError: No module named 'xxx'` | conda 环境未激活 | 执行 `conda activate speakr` | | 数据库文件位置错误 | 从错误目录启动 | 确保从 `speakr 2/` 目录执行 `python .\speakr\app.py` | --- ## 环境配置 编辑 `.env` 文件,主要配置项: ```ini # ASR 服务配置 USE_ASR_ENDPOINT=true ASR_BASE_URL=http://10.100.3.22:6688 ASR_DIARIZE=true # 文本生成模型配置 TEXT_MODEL_BASE_URL=http://10.100.3.22:9800/v1 TEXT_MODEL_API_KEY=EMPTY TEXT_MODEL_NAME=minimax-m2.5 # 数据库配置 SQLALCHEMY_DATABASE_URI=sqlite:///transcriptions.db # PostgreSQL 配置示例(切换时只需修改 URI 即可) # SQLALCHEMY_DATABASE_URI=postgresql://user:password@localhost/speakr # DB_POOL_SIZE=10 # DB_MAX_OVERFLOW=20 # DB_POOL_TIMEOUT=30 # DB_POOL_RECYCLE=3600 # 外部访问端口(Nginx 监听端口) EXTERNAL_PORT=8083 # 时区配置 TIMEZONE=Asia/Shanghai ``` --- ## 重构计划与当前进度 ### 拆分原则 1. ⭐ 标记的模块是业务特色,拆分时只改代码组织结构,不改业务逻辑 2. 拆分顺序: - **第一步**:抽出 Models(最安全,纯数据定义) - **第二步**:抽出 Services(把 app.py 中的业务逻辑函数提取到独立文件) - **第三步**:抽出 API 蓝图(把路由按功能域分组注册) - **第四步**:抽出 Utils(工具函数独立化) - **第五步**:模板组件化(可选,优先级较低) 3. `app.py` 最终只保留:Flask 初始化、数据库初始化、蓝图注册、全局错误处理 4. 保持向后兼容:拆分过程中所有 API 路径不变,前端无需改动 ### 需要特别注意的业务逻辑 | 功能 | 说明 | 拆分注意事项 | |------|------|-------------| | ⭐ 声纹管理 | VoiceprintClient 类、声纹注册/比对/同步 | 保持 VoiceprintClient 与外部平台的交互逻辑不变 | | ⭐ 系统配置 | SystemConfig 动态配置读写 | 保持配置缓存和热加载机制 | | ⭐ 草稿 API | 已有独立蓝图 draft_bp | 直接迁移,几乎不需改动 | | ⭐ 标准词映射 | 转写后的文本纠正 | 保持映射规则和应用逻辑 | | ⭐ 自动处理 | 定时/触发式自动转写 | 保持调度逻辑和状态管理 | | ⭐ 收件箱 | 录音的收件箱/高亮状态 | 保持查询和状态切换逻辑 | | ⭐ 热词 | 热词管理和应用 | 保持与转写服务的集成 | ### 当前进度 #### ✅ 已完成:Phase 1 - Models 层抽取 已将 `src/app.py` 中的 **16 个数据模型** 和 **2 个表单类** 从主文件中抽取到独立模块: | 状态 | 任务 | 说明 | |------|------|------| | ✅ | `src/database.py`(已移至 clients) | 创建独立数据库实例文件,集中管理 SQLAlchemy db 对象(Phase 2.5 移至 clients/database.py) | | ✅ | `src/models/` | 每个模型独立文件(user.py, speaker.py, recording.py 等 16 个模型) | | ✅ | `src/models/forms.py` | 表单类独立(RegistrationForm, LoginForm, password_check) | | ✅ | `src/utils/markdown.py` | Markdown 转 HTML 工具函数 | | ✅ | `src/utils/datetime.py` | 时区转换工具函数 | | ✅ | `src/app.py` | 删除模型定义,改为从 `src.models` 导入 | **验证结果**:应用可正常启动,数据库文件路径正确(`speakr/instance/transcriptions.db`) #### ✅ 已完成:Phase 2 - Services 层抽取 **服务文件已全部创建**(`src/services/` 目录下 11 个服务文件),app.py 中的旧函数定义已全部删除。 | 状态 | 服务文件 | 包含函数 | |------|---------|---------| | ✅ | `llm_service.py` | `call_llm_completion`, `clean_llm_response`, `format_transcription_for_llm`, `process_streaming_with_thinking`, `extract_thinking_content`, `preprocess_long_transcription`, `extract_corrected_text`, `format_api_error_message` | | ✅ | `transcription_service.py` | `transcribe_audio_asr`, `transcribe_audio_task`, `transcribe_single_file`, `transcribe_with_chunking`, `extract_audio_from_video`, `convert_to_wav` | | ✅ | `summary_service.py` | `generate_title_task`, `generate_summary_only_task` | | ✅ | `embedding_service.py` | `get_embedding_model`, `chunk_transcription`, `generate_embeddings`, `serialize_embedding`, `deserialize_embedding`, `process_recording_chunks`, `basic_text_search_chunks`, `semantic_search_chunks` | | ✅ | `event_service.py` | `extract_events_from_transcript`, `generate_ics_content`, `escape_ical_text` | | ✅ | `document_service.py` | `process_markdown_to_docx`, `get_recording_display_title`, `sanitize_download_filename_component`, `build_docx_download_filename`, `set_download_filename_header` | | ✅ | `speaker_service.py` | `update_speaker_usage`, `identify_speakers_from_text`, `identify_unidentified_speakers_from_text` | | ✅ | `voiceprint_service.py` | `sync_voiceprint_to_platform`(VoiceprintClient 已移至 clients 层) | | ✅ | `auto_process_service.py` | `initialize_file_monitor`, `get_file_monitor_functions` | | ✅ | `auth_service.py` | `is_safe_url`, `auto_login` | | ✅ | `__init__.py` | 统一导出所有服务函数 | **已完成的工作:** 1. ✅ 删除了 app.py 中的 22 个重复函数定义,改为从服务层导入 2. ✅ 创建了 `src/utils/text_utils.py` 和 `src/utils/json_utils.py`,将工具函数移出 app.py 3. ✅ 添加了服务层和工具层的导入 4. ✅ 核心功能测试通过,应用运行正常 #### ✅ 已完成:Phase 2.5 - Clients 层创建 按照行业规范创建了 `src/clients/` 层,统一管理与外部服务的连接客户端: | 状态 | 客户端 | 说明 | |------|--------|------| | ✅ | `database.py` | 数据库客户端:`db` 实例 + 配置 + `init_database()` 初始化逻辑 | | ✅ | `llm_client.py` | LLM 客户端:OpenAI 客户端初始化(client, TEXT_MODEL_NAME, TEXT_MODEL_API_KEY 等) | | ✅ | `voiceprint_client.py` | 声纹客户端:VoiceprintClient 类 + voiceprint_client() 延迟初始化 | | ✅ | `__init__.py` | 统一导出所有客户端 | **重构成果:** - 删除了旧 `src/database.py`(空实例文件),合并到 `clients/database.py` - 删除了 `services/voiceprint_service.py` 中的 `VoiceprintClient` 类,移到 `clients/voiceprint_client.py` - 17 个模型文件 + 6 个服务文件的 `db` 导入统一改为 `from src.clients import db` - 消除了层层传递 `client` 参数的问题 - 外部服务客户端与业务逻辑分离,架构更清晰 #### ✅ 已完成:Phase 2.6 - 服务层注释中文化 将所有服务层文件中的英文注释和日志消息翻译为中文,提升代码可读性: | 文件 | 翻译内容 | |------|---------| | `clients/llm_client.py` | URL 注释清理逻辑、客户端初始化逻辑 | | `clients/voiceprint_client.py` | 声纹客户端注释和日志(新建文件,全中文) | | `clients/database.py` | 数据库配置和初始化注释(新建文件,全中文) | | `services/llm_service.py` | 思考内容提取、流式处理、API 错误格式化、分段总结等函数的注释和日志 | | `services/summary_service.py` | 摘要生成全流程的注释和日志(标签提示词、语言要求、系统/用户消息构建等) | | `services/transcription_service.py` | 音频提取、ASR 转录、分块转录、Whisper 调用等全流程注释和日志 | | `services/embedding_service.py` | 文本分块、嵌入向量生成、语义搜索等全流程注释和日志 | | `services/voiceprint_service.py` | 声纹同步业务逻辑注释 | #### ✅ 已完成:Phase 3 - API 蓝图抽取 将 `src/app.py` 中的约 100 个路由定义按功能域分组抽取到独立的 Flask 蓝图中,app.py 从约 10000 行精简到约 700 行: | 状态 | 蓝图文件 | 路由数量 | 说明 | |------|---------|---------|------| | ✅ | `api/auth.py` | 5 | 注册、登录、登出、账户管理、修改密码 | | ✅ | `api/recording.py` | 24 | 录音 CRUD、上传、转录、下载、状态管理、ASR 校正 | | ✅ | `api/share.py` | 7 | 分享创建、查看、访问 | | ✅ | `api/tag.py` | 6 | 标签 CRUD、应用到录音 | | ✅ | `api/speaker.py` | 5 | 说话人管理、搜索 | | ✅ | `api/admin.py` | 18 | 管理后台、统计分析、录音管理、配置管理 | | ✅ | `api/search.py` | 7 | 搜索、Inquire 对话、收件箱 | | ✅ | `api/voiceprint.py` | 6 | 声纹注册、管理、同步 | | ✅ | `api/config.py` | 6 | 系统配置读写、分段服务管理 | | ✅ | `api/hotword.py` | 2 | 热词管理 | | ✅ | `api/template.py` | 5 | 转录模板 CRUD | | ✅ | `api/event.py` | 3 | 事件提取、ICS 导出 | | ✅ | `api/main.py` | 5 | 主页、摘要生成、对话 | **架构改进:** - 创建 `api/__init__.py` 中的 `register_blueprints()` 函数,统一管理所有蓝图注册和初始化 - 使用 `init_xxx_bp()` 延迟注入模式解决循环导入问题(limiter、csrf、bcrypt 等全局对象) - 所有 API 路径保持不变,前端无需任何修改 - `app.py` 仅保留:Flask 初始化、配置加载、蓝图注册、全局错误处理、启动逻辑 **重构后修复的问题:** | 问题 | 原因 | 解决方案 | |------|------|---------| | 重定向循环 | `main.py` 中的 `index()` 路由误加了 `@login_required` 装饰器,丢失了自动登录逻辑 | 恢复备份中的 token 验证和自动登录逻辑,移除装饰器 | | WebSocket 连接失败 | `getUserConfig` 路由缺少 `FUNASR_WEBSOCKET_IP` 和 `SCREEN_PUSH_IP` 配置 | 在 `main.py` 的 `get_user_config()` 中补充 WebSocket 配置字段 | | 循环导入 bcrypt | `main.py` 在模块级别导入 `bcrypt` 导致循环依赖 | 改为在 `init_main_bp()` 中延迟注入 `bcrypt_instance` | #### ✅ 已完成:统一配置管理 创建 `src/config.py` 统一配置文件,将所有散落的环境变量读取集中管理: | 配置域 | 包含变量 | |--------|---------| | 基础配置 | `SECRET_KEY`, `UPLOAD_FOLDER`, `LOG_LEVEL`, `MAX_FILE_SIZE_MB` | | ASR 配置 | `USE_ASR_ENDPOINT`, `ASR_BASE_URL`, `ASR_DIARIZE`, `ASR_MIN_SPEAKERS`, `ASR_MAX_SPEAKERS` | | LLM 配置 | `TEXT_MODEL_API_KEY`, `TEXT_MODEL_BASE_URL`, `TEXT_MODEL_NAME` | | 分段配置 | `ENABLE_CHUNKING` | | Inquire 配置 | `ENABLE_INQUIRE_MODE` | | 数据库配置 | `SQLALCHEMY_DATABASE_URI`, `DB_POOL_SIZE`, `DB_MAX_OVERFLOW`, `DB_POOL_TIMEOUT`, `DB_POOL_RECYCLE` | | 外部 API | `TRANSCRIPTION_BASE_URL`, `VALID_TOKEN`, `EMBEDDINGS_AVAILABLE` | | 代理配置 | `HTTP_PROXY`, `HTTPS_PROXY` | **重构成果:** - `clients/llm_client.py`、`clients/voiceprint_client.py` 从 `src.config` 导入配置 - `clients/__init__.py` 统一导出所有配置项 - `app.py` 通过 `from src.clients import ...` 统一导入配置,消除散落的环境变量读取 - 提供 `validate_config()` 函数在启动时验证关键配置 #### ✅ 已完成:Phase 3.5 - app.py 清理与同步服务抽取 将 `src/app.py` 中残留的约 400 行业务逻辑彻底清理,app.py 从约 700 行精简到约 234 行: | 状态 | 任务 | 说明 | |------|------|------| | ✅ | 删除重复的 preprocess 函数 | 删除了 `preprocess_long_transcription` 等 6 个重复定义的函数(已在 llm_service.py 中) | | ✅ | 抽取远程同步服务 | 创建了 `services/sync_service.py`,将约 180 行同步逻辑从 app.py 移出 | | ✅ | 清理无用 import | 删除了 30+ 个未使用的导入(render_template、OpenAI、httpx、subprocess、markdown 等) | | ✅ | 注释中文化 | 将所有英文注释翻译为中文,删除无意义注释 | | ✅ | 修复函数签名 | `start_sync_thread()` 增加 `app` 参数,调用处同步更新 | | ✅ | 优化配置读取 | `preprocess_long_transcription` 等函数直接从 `src.config` 读取配置,不再通过参数传递 | #### ✅ 已完成:Phase 4 - app.py 工厂模式重构 采用 Flask `create_app()` 工厂模式,将 `src/app.py` 从 234 行进一步精简到 60 行,各层职责清晰分离: **新建模块:** | 文件 | 职责 | |------|------| | `src/logging_config.py` | 日志配置模块 | | `src/flask_ext.py` | Flask 扩展实例集合(login_manager、bcrypt、limiter、csrf) | | `src/services/register.py` | 服务层统一初始化(音频分段、同步线程、文件监控、表结构迁移) | | `src/api/template_filters.py` | 模板过滤器注册 | **修改模块:** | 文件 | 修改内容 | |------|---------| | `src/config.py` | 新增 `get_app_config()` 函数,一次性返回所有需写入 `app.config` 的配置项。新增配置只需改此函数,无需修改 `app.py` | | `src/clients/__init__.py` | 集中管理 `chunking_service` 和 `EMBEDDINGS_AVAILABLE` 的初始化与导出 | | `src/api/__init__.py` | 简化 `register_blueprints()` 函数,直接从 `src.flask_ext` 导入扩展实例 | | `src/app.py` | 采用 `create_app()` 工厂模式,仅负责编排各层初始化顺序 | **`app.py` 最终结构(60 行):** ``` create_app() ├── 1. configure_logging() ← src/logging_config.py ├── 2. 创建 Flask 应用实例 ├── 3. 加载应用配置 ← src/config.py 的 get_app_config() ├── 4. 配置反向代理支持 ├── 5. 初始化 Flask 扩展 ← src/flask_ext.py ├── 6. 初始化数据库 ← src/clients/database.py ├── 7. 注册蓝图 ← src/api/__init__.py ├── 8. 注册模板过滤器 ← src/api/template_filters.py └── 9. 初始化服务层 ← src/services/register.py ``` **关键设计决策:** | 决策 | 理由 | |------|------| | `flask_ext.py` 独立 | Flask 扩展是全局单例,不应放在 clients 或 services 中 | | `get_app_config()` 集中管理 | 新增配置只需改 config.py,app.py 永远不需要改动 | | `register_blueprints` 自行读扩展 | 蓝图注册最清楚自己需要什么扩展,无需 app.py 中转 | | `init_services` 放在最后 | 服务依赖数据库和蓝图,必须在它们之后初始化 | --- ## 开发规范 ### API 接口返回格式规范 > **重要:所有新建/修改的 API 接口必须使用 `ApiResponse` 工具类统一返回格式。旧接口暂不强制迁移。** #### 工具位置 `src/api/api_response.py` #### 使用方式 ```python from src.api.api_response import ApiResponse # 成功响应(返回数据) @blueprint.route('/xxx', methods=['POST']) def create_xxx(): result = do_something() return ApiResponse.success({'xxx': result.to_dict()}) # 成功响应(带提示消息) return ApiResponse.success({'xxx': result.to_dict()}, '创建成功') # 成功响应(自定义状态码,如 201 Created) return ApiResponse.success({'xxx': result.to_dict()}, '创建成功', status_code=201) # 错误响应 if not xxx: return ApiResponse.error('资源未找到', 404) # 错误响应(带详情) if not valid: return ApiResponse.error('参数校验失败', 400, detail='email 格式不正确') # 分页响应 return ApiResponse.paginated( items=[item.to_dict() for item in items], pagination={ 'page': page, 'per_page': per_page, 'total': total, 'total_pages': total_pages, 'has_next': has_next, 'has_prev': has_prev } ) ``` #### 响应格式约定 **成功响应结构:** ```json { "success": true, "message": "操作成功(可选)", "xxx": {...} // 业务数据直接合并到顶层 } ``` **分页响应结构:** ```json { "success": true, "items": [...], "pagination": { "page": 1, "total": 100, ... } } ``` **错误响应结构:** ```json { "error": "用户友好的错误提示" } ``` #### 注意事项 1. `data` 参数必须是字典类型,列表数据请使用 `paginated()` 或直接合并到字典中 2. 前端通过 `response.ok`(HTTP 状态码)判断成功/失败,通过 `data.error` 获取错误信息 3. 旧接口暂不迁移,新接口和修改的接口必须使用 `ApiResponse` 4. 不要在 `ApiResponse` 中直接传递原始异常信息给用户(`str(e)`),应转为友好提示 --- ## 技术栈 - **后端**: Python/Flask + SQLAlchemy - **前端**: Vue.js 3 + Tailwind CSS - **AI**: OpenAI Whisper (ASR) + Ollama/OpenRouter (LLM) - **数据库**: SQLite(默认)/ PostgreSQL(支持环境变量切换) - **部署**: Nginx 反向代理 + Flask 开发服务器 --- ## 依赖安装 ```bash conda activate speakr pip install -r requirements.txt ``` 主要依赖: - `flask==2.3.3` - `flask-sqlalchemy==3.1.1` - `flask-login==0.6.3` - `flask-wtf==1.2.2` - `flask-bcrypt==1.0.1` - `openai==1.3.0` - `markdown==3.5.1` - `python-docx==1.1.0` --- ## 许可证 本项目基于 AGPL v3.0 开源协议。