├── Dockerfile ├── LICENSE ├── README.md ├── dockerRun.sh ├── edge-tts.py ├── gunicorn.conf ├── requirements.txt └── wx.jpg /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8.4 2 | COPY requirements.txt ./ 3 | RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 4 | COPY . /flask_project/ 5 | #ADD ./tts.py /flask_project/ 6 | WORKDIR /flask_project/ 7 | 8 | CMD ["gunicorn", "edge-tts:app", "-c","gunicorn.conf"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Roronoa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 系统说明 3 | 4 | - 基于 微软edge的在线语音合成服务,实现文字转语音 5 | - 代码采用 flask+ edge-tts +python3.9+gunicorn + cos 直接可以运行在docker项目中,接口根据文字、主播 生成语音并上传到腾讯云COS云存储 6 | 7 | 8 | 9 | ## 小程序体验 10 | 我用本项目代码 + uniapp 做了一个文字转语音的微信小程序,可以扫码试用下看下效果 11 | 12 | ![](wx.jpg) 13 | 14 | 15 | ### 本地运行 16 | ``` 17 | # 国内镜像下载python库 18 | pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 19 | 20 | #python直接运行 接口服务器 21 | python3 edge-tts.py 22 | 23 | #浏览器接口访问 24 | http://127.0.0.1:2020/dealAudio?text=欢迎使用tts&file_name=1.mp3&voice=xiaoxiao 25 | 26 | ``` 27 | 28 | 29 | ### 服务器部署 30 | ``` 31 | # 文件上传到服务器之后,直接运行dockerRun.sh 就可以了 32 | 33 | [root@VM_43_255_centos python_tts]# ./dockerRun.sh 34 | python_tts 35 | python_tts 36 | Sending build context to Docker daemon 17.96MB 37 | Step 1/6 : FROM python:3.8.4 38 | ---> ea8c3fb3cd86 39 | Step 2/6 : COPY requirements.txt ./ 40 | ---> Using cache 41 | ---> 0c97033f1256 42 | Step 3/6 : RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 43 | ---> Using cache 44 | ---> f194e15e3fcd 45 | Step 4/6 : COPY . /flask_project/ 46 | ---> Using cache 47 | ---> 92b41981b287 48 | Step 5/6 : WORKDIR /flask_project/ 49 | ---> Using cache 50 | ---> 0b7e9dc8eb16 51 | Step 6/6 : CMD ["gunicorn", "edge-tts:app", "-c","gunicorn.conf"] 52 | ---> Using cache 53 | ---> e4bb9421777d 54 | Successfully built e4bb9421777d 55 | Successfully tagged python_tts:latest 56 | 27607380de042b36f678167160e176749f78b44a903c08a7ebde76868a3c5aa4 57 | 58 | #docker服务创建完成 通过外网接口调用即可 59 | 60 | ``` 61 | 62 | 63 | 静待音频生成之后就可以听到 "网红晓晓" 的声音了,tts支持的语音有很多 64 | 本项目只是用了中文发音的主播,如:晓晓、云希、云杨等都是抖音里的常用网红主播... 65 | 66 | 67 | ``` 68 | #查看和扩展的声音 69 | edge-tts --list-voices 70 | ``` 71 | 72 | 73 | 74 | #### 扩展-数字人开口说话 75 | 76 | - 搭配live2d数字人模型,配合音频生成实现开口说话 [live2dSpeek](https://github.com/lyz1810/live2dSpeek) 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /dockerRun.sh: -------------------------------------------------------------------------------- 1 | docker stop python_tts 2 | docker rm python_tts 3 | 4 | docker build -t python_tts . 5 | docker run --name python_tts -p 2020:2020 \ 6 | -d python_tts 7 | 8 | 9 | -------------------------------------------------------------------------------- /edge-tts.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import re 4 | import sys 5 | 6 | from flask import Flask, request 7 | from flask_cors import CORS 8 | from qcloud_cos import CosConfig, CosS3Client 9 | 10 | 11 | app = Flask(__name__, static_folder='tts') # 指定静态文件夹 12 | CORS(app) # 这样设置允许所有来源的请求 13 | 14 | 15 | #上传到COS 16 | def uploadCos(file_path,relativePath): 17 | # 腾讯云COSV5Python SDK, 目前可以支持Python2.6与Python2.7以及Python3.x 18 | # pip安装指南:pip install -U cos-python-sdk-v5 19 | # cos最新可用地域,参照https://www.qcloud.com/document/product/436/6224 20 | logging.basicConfig(level=logging.INFO, stream=sys.stdout) 21 | 22 | # 设置用户属性, 包括 secret_id, secret_key, region等。Appid 已在CosConfig中移除,请在参数 Bucket 中带上 Appid。Bucket 由 BucketName-Appid 组成 23 | region = '' # 替换为用户的 region,已创建桶归属的region可以在控制台查看,https://console.cloud.tencent.com/cos5/bucket 24 | secret_id = '' # 替换为用户的 SecretId,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi 25 | secret_key = '' # 替换为用户的 SecretKey,请登录访问管理控制台进行查看和管理,https://console.cloud.tencent.com/cam/capi 26 | bucket_name = '' 27 | 28 | # COS支持的所有region列表参见https://www.qcloud.com/document/product/436/6224 29 | token = None # 如果使用永久密钥不需要填入token,如果使用临时密钥需要填入,临时密钥生成和使用指引参见https://cloud.tencent.com/document/product/436/14048 30 | domain = None # domain可以不填,此时使用COS区域域名访问存储桶。domain也可以填写用户自定义域名,或者桶的全球加速域名 31 | config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Domain=domain) # 获取配置对象 32 | client = CosS3Client(config) 33 | # 文件流 简单上传 34 | with open(f'./{relativePath}', 'rb') as fp: 35 | response = client.put_object( 36 | Bucket=bucket_name, 37 | Body=fp, 38 | Key=relativePath, 39 | StorageClass='STANDARD', 40 | ContentType='audio/mpeg' 41 | ) 42 | print(response['ETag']) 43 | # 上传完成之后删除文件 44 | os.remove(file_path) # 删除文件 45 | 46 | # 构建文件的访问 URL 47 | url = f"https://{bucket_name}.cos.{region}.myqcloud.com{relativePath}" 48 | print("文件访问路径:", url) 49 | return url 50 | 51 | voiceMap = { 52 | "xiaoxiao": "zh-CN-XiaoxiaoNeural", 53 | "xiaoyi": "zh-CN-XiaoyiNeural", 54 | "yunjian": "zh-CN-YunjianNeural", 55 | "yunxi": "zh-CN-YunxiNeural", 56 | "yunxia": "zh-CN-YunxiaNeural", 57 | "yunyang": "zh-CN-YunyangNeural", 58 | "xiaobei": "zh-CN-liaoning-XiaobeiNeural", 59 | "xiaoni": "zh-CN-shaanxi-XiaoniNeural", 60 | "hiugaai": "zh-HK-HiuGaaiNeural", 61 | "hiumaan": "zh-HK-HiuMaanNeural", 62 | "wanlung": "zh-HK-WanLungNeural", 63 | "hsiaochen": "zh-TW-HsiaoChenNeural", 64 | "hsioayu": "zh-TW-HsiaoYuNeural", 65 | "yunjhe": "zh-TW-YunJheNeural", 66 | } 67 | 68 | 69 | def getVoiceById(voiceId): 70 | return voiceMap.get(voiceId) 71 | 72 | 73 | # 删除html标签 74 | def remove_html(string): 75 | regex = re.compile(r'<[^>]+>') 76 | return regex.sub('', string) 77 | 78 | 79 | def createAudio(text, file_name, voiceId): 80 | new_text = remove_html(text) 81 | print(f"Text without html tags: {new_text}") 82 | voice = getVoiceById(voiceId) 83 | if not voice: 84 | return "error params" 85 | 86 | pwdPath = os.getcwd() 87 | #本地路径 88 | filePath = pwdPath + "/tts/" + file_name 89 | #相对路径 90 | relativePath = "/tts/" + file_name 91 | dirPath = os.path.dirname(filePath) 92 | if not os.path.exists(dirPath): 93 | os.makedirs(dirPath) 94 | if not os.path.exists(filePath): 95 | # 用open创建文件 兼容mac 96 | open(filePath, 'a').close() 97 | 98 | script = 'edge-tts --voice ' + voice + ' --text "' + new_text + '" --write-media ' + filePath 99 | os.system(script) 100 | #这里可以选择上传云存储和本地使用 101 | # 上传到腾讯云COS云存储-返回云存储地址 102 | # url = uploadCos(filePath, relativePath) 103 | 104 | # 音频保存到本地-直接返回音频地址 105 | url = f'http://127.0.0.1:2020/{relativePath}' 106 | return url 107 | 108 | 109 | def getParameter(paramName): 110 | if request.args.__contains__(paramName): 111 | return request.args[paramName] 112 | return "" 113 | 114 | @app.route('/dealAudio',methods=['POST','GET']) 115 | def dealAudio(): 116 | text = getParameter('text') 117 | file_name = getParameter('file_name') 118 | voice = getParameter('voice') 119 | return createAudio(text, file_name, voice) 120 | 121 | # 添加一个路由来处理静态文件的请求 122 | @app.route('/static/') 123 | def serve_static(filename): 124 | return send_from_directory(app.static_folder, filename) 125 | 126 | 127 | @app.route('/') 128 | def index(): 129 | return 'welcome to my tts!' 130 | 131 | if __name__ == "__main__": 132 | app.run(port=2020,host="127.0.0.1",debug=True) 133 | -------------------------------------------------------------------------------- /gunicorn.conf: -------------------------------------------------------------------------------- 1 | # gunicorn.conf 2 | 3 | # 并行工作进程数 4 | workers = 4 5 | # 指定每个工作者的线程数 6 | threads = 2 7 | # 监听内网端口5000 8 | bind='0.0.0.0:2020' 9 | # 设置守护进程,将进程交给supervisor管理 10 | daemon = 'false' 11 | # 工作模式协程 12 | worker_class = 'gevent' 13 | # 设置最大并发量 14 | worker_connections = 2000 15 | # 设置进程文件目录 16 | pidfile = 'gunicorn.pid' 17 | # 设置访问日志和错误信息日志路径 18 | accesslog = 'gunicorn_acess.log' 19 | errorlog = 'gunicorn_error.log' 20 | # 设置日志记录水平 21 | loglevel = 'debug' 22 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==3.0.2 2 | flask-cors 3 | cos-python-sdk-v5 4 | gunicorn==21.2.0 5 | gevent==24.2.1 6 | edge-tts==6.1.18 7 | -------------------------------------------------------------------------------- /wx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lyz1810/edge-tts/3d9fa80ad6ec06571f6a05ea2e5dc3aed33fd628/wx.jpg --------------------------------------------------------------------------------