Happy_language/app/routes/voice_clone.py
2025-09-22 06:06:19 +08:00

167 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
语音克隆功能路由 - 专门处理用户声音克隆
"""
import os
import tempfile
import uuid
from flask import Blueprint, request, jsonify, render_template, current_app
from flask_login import login_required, current_user
from app.services.cosyvoice_service import cosyvoice_service
from app.models import VoiceSample, db
from werkzeug.utils import secure_filename
import logging
logger = logging.getLogger(__name__)
voice_clone_bp = Blueprint('voice_clone', __name__)
@voice_clone_bp.route('/voice-clone')
@login_required
def voice_clone_page():
"""语音克隆专门页面"""
user_voice_sample = current_user.get_latest_voice_sample()
return render_template('voice_clone/index.html', voice_sample=user_voice_sample)
@voice_clone_bp.route('/api/voice-clone/upload', methods=['POST'])
@login_required
def upload_voice_sample():
"""上传用户语音样本进行克隆"""
try:
if 'audio' not in request.files:
return jsonify({
"success": False,
"message": "请选择音频文件"
})
file = request.files['audio']
if file.filename == '':
return jsonify({
"success": False,
"message": "请选择音频文件"
})
# 生成唯一文件名
unique_id = str(uuid.uuid4())[:8]
filename = f"voice_sample_{current_user.id}_{unique_id}.wav"
# 保存到临时目录
temp_dir = tempfile.gettempdir()
file_path = os.path.join(temp_dir, filename)
file.save(file_path)
# 识别语音内容
recognized_text = cosyvoice_service.recognize_audio(file_path)
# 保存到数据库
voice_sample = VoiceSample(
user_id=current_user.id,
original_audio_url=file_path,
file_size=os.path.getsize(file_path),
recognized_text=recognized_text,
clone_model_status='ready' # 简化流程直接标记为ready
)
db.session.add(voice_sample)
db.session.commit()
logger.info(f"用户 {current_user.id} 上传语音样本成功: {filename}")
return jsonify({
"success": True,
"message": "语音样本上传成功AI已经学会你的声音了",
"sample_id": voice_sample.id,
"recognized_text": recognized_text,
"file_info": {
"size": voice_sample.file_size,
"duration": float(voice_sample.duration) if voice_sample.duration else None
}
})
except Exception as e:
logger.error(f"语音样本上传失败: {str(e)}")
return jsonify({
"success": False,
"message": f"上传失败: {str(e)}"
})
@voice_clone_bp.route('/api/voice-clone/generate', methods=['POST'])
@login_required
def generate_cloned_speech():
"""使用用户的声音克隆生成语音"""
try:
data = request.get_json()
text = data.get('text', '')
if not text:
return jsonify({
"success": False,
"message": "请输入要合成的文本"
})
# 获取用户最新的语音样本
voice_sample = current_user.get_latest_voice_sample()
if not voice_sample:
return jsonify({
"success": False,
"message": "请先录制语音样本"
})
# 使用CosyVoice进行语音克隆
stream_audio, full_audio = cosyvoice_service.generate_speech_with_voice_cloning(
text=text,
reference_audio_path=voice_sample.original_audio_url,
reference_text=voice_sample.recognized_text or "",
seed=42
)
if full_audio:
return jsonify({
"success": True,
"message": "用你的声音说话成功!",
"audio_url": full_audio,
"original_text": text
})
else:
return jsonify({
"success": False,
"message": "语音生成失败,请重试"
})
except Exception as e:
logger.error(f"语音克隆生成失败: {str(e)}")
return jsonify({
"success": False,
"message": f"生成失败: {str(e)}"
})
@voice_clone_bp.route('/api/voice-clone/status', methods=['GET'])
@login_required
def get_voice_clone_status():
"""获取用户语音克隆状态"""
try:
voice_sample = current_user.get_latest_voice_sample()
if not voice_sample:
return jsonify({
"success": True,
"has_sample": False,
"status": "no_sample",
"message": "还没有录制语音样本哦!快来录制你的专属声音吧"
})
return jsonify({
"success": True,
"has_sample": True,
"status": voice_sample.clone_model_status,
"sample_id": voice_sample.id,
"recognized_text": voice_sample.recognized_text,
"upload_time": voice_sample.upload_time.strftime("%Y-%m-%d %H:%M"),
"message": "你的专属声音已准备好!"
})
except Exception as e:
logger.error(f"获取语音克隆状态失败: {str(e)}")
return jsonify({
"success": False,
"message": f"获取状态失败: {str(e)}"
})