3 Star 5 Fork 2

新媒体网络营销/针对cosyvoice开发的大文本转语音处理工具_听书狂人处理机

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
app.py 38.52 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
from flask import Flask, request, render_template, jsonify
import requests
import os
import threading
from datetime import datetime
from bs4 import BeautifulSoup
import webbrowser
import time
import sqlite3
import re
import logging
from logging.handlers import RotatingFileHandler
from pydub import AudioSegment
import jieba
import markdown
from title_extractor import find_book_title
from text_fc import preprocess_text, split_text
from database_utils import (
init_db, clear_database, record_submission_info, get_submission_info,
has_unfinished_sentences, get_unfinished_progress, get_unfinished_sentence,
get_progress_summary, save_unfinished_progress, clear_unfinished_progress,
save_sentences_to_db, load_sentences_from_db, update_sentence_status,
update_sentence_duration, shrink_database, get_progress_data,
get_chapter_and_section_counts, get_total_audio_duration, get_avg_section_duration,
get_default_speaker, update_filename, get_all_speakers, update_default_speaker, add_speaker_to_db, delete_speaker_from_db, get_custom_settings,
update_custom_settings, restore_defaults, get_custom_url, log_error_to_detailed_info, update_sentence_processing_time # 确保引用正确函数
)
# 导入四个合并模块
from merge_audioA import trigger_merge_logic as merge_audio_A_logic
from merge_audioB import trigger_merge_logic as merge_audio_B_logic
from merge_audioC import trigger_merge_logic as merge_audio_C_logic
from merge_audioD import trigger_merge_logic as merge_audio_D_logic
from merge_audioB import get_merge_status
app = Flask(__name__)
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
handler = RotatingFileHandler('app.log', maxBytes=1000000, backupCount=10, encoding='utf-8')
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# 确保 temp 和 output 目录存在
if not os.path.exists('./temp'):
os.makedirs('./temp')
if not os.path.exists('./output'):
os.makedirs('./output')
if not os.path.exists('./db'):
os.makedirs('./db')
# 全局变量用于跟踪进度和控制消息发送
progress = {"status": "idle", "sentence": "", "index": 0, "total": 0, "merged_filename": ""}
send_messages = True
fix_completed = False # 用于跟踪修复进程完成状态
fix_progress = {"total_gaps": 0, "processed_gaps": 0, "current_gap": None} # 用于跟踪修复进度
# 清理 temp 目录
def clear_temp_directory():
for filename in os.listdir('./temp'):
file_path = os.path.join('./temp', filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
except Exception as e:
logger.error(f'Failed to delete {file_path}. Reason: {e}')
# 捕获书名的正则表达式逻辑
def capture_title(text):
title_regex = re.compile(r'《(.+?)》')
match = title_regex.search(text)
if match:
return match.group(1)
return None
# 过滤函数,去除行首空格和疑似转义字符,并移除回车和换行符
def filter_text_for_sending(text):
text = text.lstrip()
text = re.sub(r'[\\/*?:"<>|]', "", text)
text = text.replace('\r', '').replace('\n', '')
return text
# 获取音频时长
def get_audio_duration(filepath):
audio = AudioSegment.from_file(filepath)
return len(audio) # 返回时长(毫秒)
# 获取当前的 filename
@app.route('/get_filename', methods=['GET'])
def get_filename():
filename, _ = get_submission_info()
logger.debug(f"Retrieved filename: {filename}")
return jsonify({"filename": filename if filename else ""})
# 更新 filename
@app.route('/update_filename', methods=['POST'])
def update_filename_route():
data = request.json
new_filename = data.get('filename', '')
try:
# 使用新的函数,确保只更新 filename 字段,不影响其他字段
update_filename(new_filename)
logger.debug(f"Updated filename to: {new_filename}")
return jsonify({"success": True})
except Exception as e:
logger.error(f"Error updating filename: {e}")
return jsonify({"success": False, "error": str(e)})
# 时间选择器逻辑,根据总时长和章节信息选择不同的合并策略
def time_selector(total_duration, num_chapters, num_sections, avg_section_duration):
logger.info("正在根据音频时长和章节信息选择合并策略...")
if total_duration >= 2 * 3600 * 1000 and num_chapters > 0 and num_sections > 0 and avg_section_duration >= 30 * 60 * 1000:
logger.info("选择策略 A: 按章节合并...")
return "A"
elif total_duration >= 2 * 3600 * 1000 and num_chapters > 0 and (num_sections == 0 or avg_section_duration < 30 * 60 * 1000):
logger.info("选择策略 B: 按大章节合并...")
return "B"
elif 55 * 60 * 1000 <= total_duration < 2 * 3600 * 1000:
logger.info("选择策略 C: 按时长合并...")
return "C"
else:
logger.info("选择策略 D: 全文合并...")
return "D"
# 保存文件时确保文件名唯一
def save_unique_file(temp_path, dest_dir, base_filename):
file_index = 1
filename = base_filename
dest_path = os.path.join(dest_dir, filename)
while os.path.exists(dest_path):
filename = f"{os.path.splitext(base_filename)[0]}_r{file_index}{os.path.splitext(base_filename)[1]}"
dest_path = os.path.join(dest_dir, filename)
file_index += 1
os.rename(temp_path, dest_path)
return filename
# 检查 original_order 的连续性
def check_for_gaps():
conn = sqlite3.connect('./db/data.db')
cursor = conn.cursor()
cursor.execute('''SELECT original_order, status FROM split_sentences ORDER BY original_order''')
rows = cursor.fetchall()
conn.close()
last_completed_order = None
gaps = []
for row in rows:
current_order, status = row
if status == "completed":
if last_completed_order is not None and current_order > last_completed_order + 1:
gaps.append((last_completed_order + 1, current_order - 1))
last_completed_order = current_order
return gaps
@app.route('/check_gaps', methods=['GET'])
def check_gaps_route():
gaps = check_for_gaps()
if gaps:
return jsonify({"gaps_found": True, "gaps": gaps})
return jsonify({"gaps_found": False})
# 主页面路由
@app.route('/')
def index():
init_db()
chapter_count, total_count, completed_count, remaining_count = get_progress_summary()
progress_summary = f"当前捕获到合计{chapter_count}章文字,全文拆分{total_count}句进行处理,已经处理{completed_count}句,剩余{remaining_count}句。"
unfinished_sentence = ""
if has_unfinished_sentences():
row = get_unfinished_progress()
if row:
unfinished_sentence = get_unfinished_sentence()
return render_template(
'index.html',
unfinished=True if unfinished_sentence else False,
sentence=row[1] if unfinished_sentence else "",
progress_sentence=unfinished_sentence,
progress_summary=progress_summary
)
if not has_unfinished_sentences():
# 如果没有未完成任务,清空 filename
update_filename('') # 通过调用函数清空 filename
# 渲染主界面
return render_template('index.html')
# 获取未完成的句子
@app.route('/get_unfinished_sentence', methods=['GET'])
def get_unfinished_sentence_route():
unfinished_sentence = get_unfinished_sentence()
if unfinished_sentence:
return jsonify({"sentence": unfinished_sentence})
return jsonify({"sentence": ""})
# 检查修复进程是否完成
@app.route('/check_fix_completion', methods=['GET'])
def check_fix_completion():
global fix_completed
if fix_completed:
fix_completed = False # 重置状态
return jsonify({"fix_completed": True})
return jsonify({"fix_completed": False})
@app.route('/fix_progress', methods=['GET'])
def get_fix_progress():
return jsonify(fix_progress)
# 接收大文本输入的端点(用户点击“提交”时触发)
@app.route('/process_text', methods=['POST'])
def process_text():
try:
logger.info("Received text for processing.")
text = request.form['text']
# 首先从数据库中读取 filename
custom_filename, _ = get_submission_info()
logger.debug(f"Initial filename from DB: {custom_filename}")
# 再次检查表单中传入的 filename,以确保获取最新值
form_filename = request.form.get('filename')
if form_filename:
custom_filename = form_filename
logger.debug(f"Final filename after form check: {custom_filename}")
# 清理数据库和临时目录,但不清除 filename 字段
clear_database(clear_filename=False)
logger.debug("Database cleared (except for filename).")
clear_temp_directory()
logger.debug("Temporary directory cleared.")
# 收缩数据库以优化性能
shrink_database()
logger.debug("Database shrink completed.")
# 捕获书名逻辑:仅当 filename 为空时才执行书名捕获
if not custom_filename:
title = capture_title(text)
if title:
logger.info(f"捕获到的书名: {title}")
custom_filename = title
update_filename(custom_filename) # 更新 filename 字段
else:
logger.info("正则表达式未能捕获书名,尝试使用 jieba 进行识别...")
# 调用 find_book_title 进行书名识别
title = find_book_title(text)
if title:
logger.info(f"使用 jieba 捕获到的书名: {title}")
custom_filename = title
update_filename(custom_filename) # 更新 filename 字段
else:
logger.info("未能使用 jieba 捕获书名,继续执行后续处理流程。")
else:
logger.info(f"用户已经定义了 filename:{custom_filename},跳过书名捕获")
# 调用拆分出来的预处理和切片函数
start_time = time.time()
text = preprocess_text(text)
logger.debug(f"Text preprocessing completed.")
# 切分文本并检查是否分割成功
sentences = split_text(text)
if not sentences:
raise ValueError("未能分割出有效的句子,可能输入格式有问题。")
logger.info(f"Text splitting resulted in {len(sentences)} sentences.")
logger.debug(f'Text preprocessing and splitting took {time.time() - start_time:.2f} seconds.')
global progress, send_messages
progress = {"status": "processing", "sentence": text, "index": 0, "total": 0, "merged_filename": ""}
send_messages = True # 启动发送功能
# 过滤并切分文本并保存到数据库
start_time = time.time()
save_sentences_to_db(sentences)
logger.info(f"Sentences saved to DB.")
logger.debug(f'Saving sentences to DB took {time.time() - start_time:.2f} seconds.')
# 更新 submission_info 表格中的 first_slice 信息
first_slice = sentences[0][0] if sentences else ""
record_submission_info(custom_filename if custom_filename else "", first_slice)
# 保存进度
start_time = time.time()
conn = sqlite3.connect('./db/data.db')
cursor = conn.cursor()
cursor.execute('''INSERT INTO progress (sentence, idx, total, status, timestamp, ogg_files_count)
VALUES (?, ?, ?, ?, ?, ?)''',
(text, 0, len(sentences), "unfinished", datetime.now(), 0))
conn.commit()
conn.close()
logger.info(f"Progress saved to DB.")
logger.debug(f'Saving progress to DB took {time.time() - start_time:.2f} seconds.')
# 启动文本处理线程
thread = threading.Thread(target=process_text_thread, args=(custom_filename,))
thread.start()
logger.info("Text processing started successfully.")
return jsonify({"status": "started", "filename": custom_filename}) # 返回捕获的 filename
except Exception as e:
# 输出完整的错误信息到控制台和日志文件
logger.error(f"Process failed: {str(e)}", exc_info=True)
return jsonify({"status": "error", "message": str(e)}), 500
# 在单独线程中处理文本的函数
def process_text_thread(custom_filename):
global progress, send_messages
url = get_custom_url()
from threading import Timer
def timeout():
progress["status"] = "timeout"
save_unfinished_progress(progress["index"], progress["total"], "unfinished")
sentences = load_sentences_from_db()
progress["total"] = len(sentences)
# 超时计时器
timer = Timer(600, timeout) # 超时时间600秒
timer.start()
audio_files = []
try:
for sentence_data in sentences:
sentence = sentence_data['sentence'] # 通过字段名访问句子
idx = sentence_data['part_id'] # 通过字段名访问句子的part_id
speaker_name = sentence_data['name'] # 通过字段名访问发音人
# 如果未定义发音人,使用默认发音人
if not speaker_name:
speaker_name = get_default_speaker()
if progress["status"] == "timeout" or not send_messages:
break # 确保在退出循环时停止处理
progress["sentence"] = sentence
progress["index"] = idx
filtered_sentence = filter_text_for_sending(sentence) # 使用修正后的函数
# 使用 perf_counter 来计算处理时间
start_time = time.perf_counter()
while True:
try:
if not send_messages:
break # 如果用户点击了“清空且重置”按钮或“停止”
response = requests.get(url, params={'text': filtered_sentence, 'speaker': speaker_name, 'new': '1', 'streaming': '1'})
if response.status_code == 200:
temp_filename = f'temp_audio_{idx}.ogg'
temp_path = os.path.join('./temp', temp_filename)
# 保存下载的音频文件到临时路径
with open(temp_path, 'wb') as f:
f.write(response.content)
# 保存到最终路径,确保文件名唯一
final_filename = save_unique_file(temp_path, './temp', f'audio_{idx}.ogg')
duration = get_audio_duration(os.path.join('./temp', final_filename))
# 更新数据库中的文件名和时长
update_sentence_status(idx, "completed", final_filename)
update_sentence_duration(idx, duration)
audio_files.append(final_filename)
# 计算处理时间并写入数据库
end_time = time.perf_counter()
processing_time = int((end_time - start_time) * 1000) # 转换为毫秒
update_sentence_processing_time(idx, processing_time)
logger.info(f'Processing sentence {idx} took {processing_time / 1000:.2f} seconds.')
if idx == len(sentences) - 1:
progress["status"] = "complete"
clear_unfinished_progress()
# 获取总时长和章节信息
total_duration = get_total_audio_duration()
num_chapters, num_sections = get_chapter_and_section_counts()
avg_section_duration = get_avg_section_duration()
# 选择合并策略并触发合并
strategy = time_selector(total_duration, num_chapters, num_sections, avg_section_duration)
start_merge_process(strategy)
break
except requests.exceptions.RequestException as e:
error_message = f'Connection failed: {e}'
logger.error(error_message)
log_error_to_detailed_info(error_message)
time.sleep(5)
timer.cancel()
except Exception as e:
error_message = f"Processing failed: {str(e)}"
logger.error(error_message)
progress["status"] = "failed"
progress["sentence"] = error_message
timer.cancel()
def start_merge_process(strategy):
logger.info(f"启动合并策略: {strategy}")
try:
if strategy == 'A':
merge_audio_A_logic()
elif strategy == 'B':
merge_audio_B_logic()
elif strategy == 'C':
merge_audio_C_logic()
else:
merge_audio_D_logic()
except Exception as e:
logger.error(f"启动合并进程失败: {e}")
return jsonify({"status": "error", "message": str(e)})
return jsonify({"status": "merge triggered"})
@app.route('/merge_status', methods=['GET'])
def merge_status():
# 从数据库或内存中获取当前的合并状态
status = get_merge_status()
return jsonify(status)
@app.route('/progress', methods=['GET'])
def get_progress():
global progress
total_sentences, completed_sentences, remaining_sentences = get_progress_data()
total_duration = get_total_audio_duration()
num_chapters, num_sections = get_chapter_and_section_counts()
avg_section_duration = get_avg_section_duration()
book_title = get_submission_info()[0]
progress.update({
"total_sentences": total_sentences,
"completed_sentences": completed_sentences,
"total_duration": total_duration // 1000, # 转换为秒
"chapter_count": num_chapters,
"section_count": num_sections,
"book_title": book_title, # 捕获的书名
"current_index": progress["index"], # 当前句子的original_order
"total_index": total_sentences # 总的original_order数
})
return jsonify(progress)
@app.route('/continue', methods=['POST'])
def continue_process():
global progress, send_messages
logger.info("Received request to continue processing")
if has_unfinished_sentences():
row = get_unfinished_progress()
if row:
progress["status"] = "processing"
progress["index"] = row[2]
progress["total"] = row[3]
progress["sentence"] = get_unfinished_sentence()
# 检查并更新自定义文件名
custom_filename = request.form.get('filename')
if custom_filename:
custom_filename = sanitize_filename(custom_filename)
conn = sqlite3.connect('./db/data.db')
cursor = conn.cursor()
cursor.execute('UPDATE submission_info SET filename = ? WHERE id = 1', (custom_filename,))
conn.commit()
conn.close()
logger.info(f"Updated custom filename to {custom_filename}")
send_messages = True # 继续发送
thread = threading.Thread(target=continue_processing_thread, args=(custom_filename,))
thread.start()
return jsonify({"status": "continued"})
else:
logger.warning("No unfinished progress found or invalid progress data")
else:
logger.warning("No unfinished sentences found")
return jsonify({"status": "error", "message": "No processing in progress"}), 400
def continue_processing_thread(custom_filename):
global progress, send_messages
from threading import Timer
url = get_custom_url()
def timeout():
progress["status"] = "timeout"
save_unfinished_progress(progress["index"], progress["total"], "unfinished")
sentences = load_sentences_from_db()
progress["total"] = len(sentences)
timer = Timer(600, timeout) # 增加超时时间到300秒
timer.start()
audio_files = []
for sentence_data in sentences:
sentence = sentence_data['sentence'] # 通过字段名访问句子
idx = sentence_data['part_id'] # 通过字段名访问句子的part_id
speaker_name = sentence_data['name'] # 通过字段名访问发音人
# 如果未定义发音人,使用默认发音人
if not speaker_name:
speaker_name = get_default_speaker()
if progress["index"] > idx:
continue
if progress["status"] == "timeout" or not send_messages:
break # 确保在退出循环时停止处理
progress["sentence"] = sentence
progress["index"] = idx
filtered_sentence = filter_text_for_sending(sentence) # 过滤行首空格和疑似转义字符,并移除回车和换行符
# 使用 perf_counter 来计算处理时间
start_time = time.perf_counter()
while True:
try:
if not send_messages:
break # 如果用户点击了“清空且重置”按钮或“停止”按钮,则立即停止处理
response = requests.get(url, params={'text': filtered_sentence, 'speaker': speaker_name, 'new': '1', 'streaming': '1'})
if response.status_code == 200:
temp_filename = f'temp_audio_{idx}.ogg'
temp_path = os.path.join('./temp', temp_filename)
# 保存下载的音频文件到临时路径
with open(temp_path, 'wb') as f:
f.write(response.content)
# 保存到最终路径,确保文件名唯一
final_filename = save_unique_file(temp_path, './temp', f'audio_{idx}.ogg')
duration = get_audio_duration(os.path.join('./temp', final_filename))
# 计算处理时间并写入数据库
end_time = time.perf_counter()
processing_time = int((end_time - start_time) * 1000) # 转换为毫秒
update_sentence_processing_time(idx, processing_time)
# 更新数据库中的文件名和时长
update_sentence_status(idx, "completed", final_filename)
update_sentence_duration(idx, duration)
audio_files.append(final_filename)
logger.info(f'Processing sentence {idx} took {processing_time / 1000:.2f} seconds.')
if idx == len(sentences) - 1:
progress["status"] = "complete"
clear_unfinished_progress()
# 获取总时长和章节信息
total_duration = get_total_audio_duration()
num_chapters, num_sections = get_chapter_and_section_counts()
avg_section_duration = get_avg_section_duration()
# 选择合并策略并触发合并
strategy = time_selector(total_duration, num_chapters, num_sections, avg_section_duration)
start_merge_process(strategy)
break
except requests.exceptions.RequestException as e:
error_message = f'Connection failed: {e}'
logger.error(error_message)
log_error_to_detailed_info(error_message)
time.sleep(5)
timer.cancel()
timer = Timer(600, timeout)
timer.start()
timer.cancel()
@app.route('/save_progress', methods=['POST'])
def save_progress():
global progress
conn = sqlite3.connect('./db/data.db')
cursor = conn.cursor()
cursor.execute('''UPDATE progress SET status="saved", timestamp=? WHERE status="unfinished"''',
(datetime.now(),))
conn.commit()
conn.close()
return jsonify({"status": "progress saved"})
@app.route('/end_early', methods=['POST'])
def end_early():
global progress
progress["status"] = "complete"
clear_unfinished_progress()
return jsonify({"status": "ended early"})
# 清空并重新开始(用户点击“清空且重置”时触发)
@app.route('/clear_progress', methods=['POST'])
def clear_progress():
global progress, send_messages
# 停止发送信息的流程
send_messages = False # 停止对接口发送信息
progress["status"] = "idle" # 停止当前的处理进程
clear_temp_directory()
clear_database(clear_filename=True) # 传递 clear_filename=True 以清空 filename
# 收缩数据库
shrink_database()
return jsonify({"status": "progress cleared and reset"})
# 新增的路由处理“停止”按钮的功能
@app.route('/stop_processing', methods=['POST'])
def stop_processing():
global progress, send_messages
send_messages = False # 停止对接口发送信息
progress["status"] = "stopped"
return jsonify({"status": "processing stopped"})
# 获取详细错误信息的API端点
@app.route('/get_detailed_info', methods=['GET'])
def get_detailed_info():
conn = sqlite3.connect('./db/data.db')
cursor = conn.cursor()
cursor.execute('SELECT message, timestamp FROM detailed_info ORDER BY timestamp DESC')
errors = [{"message": row[0], "timestamp": row[1]} for row in cursor.fetchall()]
conn.close()
return jsonify({"errors": errors})
# 新增的路由处理“马上合并”按钮的功能
@app.route('/trigger_merge', methods=['POST'])
def trigger_merge():
logger.info("Received manual merge trigger request")
total_duration = get_total_audio_duration()
num_chapters, num_sections = get_chapter_and_section_counts()
avg_section_duration = get_avg_section_duration()
strategy = time_selector(total_duration, num_chapters, num_sections, avg_section_duration)
start_merge_process(strategy)
return jsonify({"status": "merge triggered"})
@app.route('/fix_gaps', methods=['POST'])
def fix_gaps():
try:
# 获取数据库中的断层信息
gaps = check_for_gaps()
if gaps:
global fix_completed, send_messages, fix_progress
fix_completed = False
send_messages = True # 启动消息发送
fix_progress = {
"total_gaps": len(gaps),
"processed_gaps": 0,
"current_gap": None
}
def fix_gaps_thread(gaps):
global send_messages, fix_completed, fix_progress # 确保全局变量在多线程中可用
url = get_custom_url()
try:
for gap in gaps:
start_order, end_order = gap
fix_progress["current_gap"] = gap # 更新当前处理的断层
for original_order in range(start_order, end_order + 1):
if not send_messages:
break # 如果用户停止修复
# 获取对应的句子
conn = sqlite3.connect('./db/data.db')
conn.row_factory = sqlite3.Row # 使用Row模式
cursor = conn.cursor()
cursor.execute('SELECT sentence, name FROM split_sentences WHERE original_order=?', (original_order,))
row = cursor.fetchone()
conn.close()
if row:
sentence = row['sentence']
speaker_name = row['name'] # 获取发音人
if not speaker_name:
speaker_name = get_default_speaker()
# 向外部端口发送请求,获取音频
# 记录开始时间
start_time = time.time()
while True:
try:
response = requests.get(url, params={'text': sentence, 'speaker': speaker_name, 'new': '1', 'streaming': '1'})
if response.status_code == 200:
temp_filename = f'temp_audio_{original_order}.ogg'
temp_path = os.path.join('./temp', temp_filename)
# 保存下载的音频文件到临时路径
with open(temp_path, 'wb') as f:
f.write(response.content)
# 记录结束时间
end_time = time.time()
processing_time = int((end_time - start_time) * 1000) # 转换为毫秒
# 保存到最终路径,确保文件名唯一
final_filename = save_unique_file(temp_path, './temp', f'audio_{original_order}.ogg')
duration = get_audio_duration(os.path.join('./temp', final_filename))
# 更新数据库中的文件名和时长
update_sentence_status(original_order, "completed", final_filename)
update_sentence_duration(original_order, duration)
# **更新数据库中的处理时间**
update_sentence_processing_time(original_order, processing_time)
logger.info(f'Fixed gap for original_order {original_order}')
break
except requests.exceptions.RequestException as e:
logger.error(f'Connection failed during gap fixing: {e}')
time.sleep(5)
fix_progress["processed_gaps"] += 1 # 更新已处理的断层数量
fix_completed = True
# 检查是否满足合并条件,满足则自动触发合并
total_duration = get_total_audio_duration()
num_chapters, num_sections = get_chapter_and_section_counts()
avg_section_duration = get_avg_section_duration()
strategy = time_selector(total_duration, num_chapters, num_sections, avg_section_duration)
start_merge_process(strategy)
# 自动刷新前端页面
logger.info("All gaps fixed. Refreshing page.")
except Exception as e:
logger.error(f"Error occurred during gap fixing: {e}")
fix_completed = False
finally:
send_messages = False
# 启动修复进程的线程
thread = threading.Thread(target=fix_gaps_thread, args=(gaps,))
thread.start()
return jsonify({"status": "fixing gaps"})
else:
return jsonify({"status": "no_gaps"})
except Exception as e:
logger.error(f"Fixing gaps failed: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/get_speakers', methods=['GET'])
def get_speakers():
speakers = get_all_speakers()
# 修改显示方式
for speaker in speakers:
if speaker['is_default']:
speaker['name'] += '(*默认讲述者*)'
return jsonify({"speakers": speakers})
# 更新默认联系人
@app.route('/update_default_speaker', methods=['POST'])
def update_default_speaker_route():
data = request.get_json()
speaker_name = data.get('speaker')
if speaker_name:
try:
update_default_speaker(speaker_name) # 调用 database_utils.py 中的函数
return jsonify({"status": "success"})
except Exception as e:
logger.error(f"Error updating default speaker: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
return jsonify({"status": "error"}), 400
@app.route('/add_speaker', methods=['POST'])
def add_speaker():
data = request.get_json()
speaker_name = data.get('speaker_name')
if speaker_name:
try:
add_speaker_to_db(speaker_name)
return jsonify({"status": "success"})
except Exception as e:
logger.error(f"Error adding speaker: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
return jsonify({"status": "error"}), 400
@app.route('/delete_speaker', methods=['POST'])
def delete_speaker():
data = request.get_json()
speaker_name = data.get('speaker_name')
if speaker_name:
try:
delete_speaker_from_db(speaker_name)
return jsonify({"status": "success"})
except Exception as e:
logger.error(f"Error deleting speaker: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
return jsonify({"status": "error"}), 400
# 获取当前设置
@app.route('/get_custom_settings', methods=['GET'])
def get_custom_settings_route():
try:
settings = get_custom_settings()
return jsonify({"status": "success", "settings": settings})
except Exception as e:
logger.error(f"Error retrieving custom settings: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
# 更新用户自定义设置
@app.route('/update_setting', methods=['POST'])
def update_setting():
data = request.json
new_setting = data.get('new_setting')
try:
update_custom_setting(new_setting)
return jsonify({"status": "success"})
except Exception as e:
logger.error(f"Error updating setting: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
# 恢复默认设置
@app.route('/restore_defaults', methods=['POST'])
def restore_defaults_route():
try:
restore_defaults()
return jsonify({"status": "success"})
except Exception as e:
logger.error(f"Error restoring defaults: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/help-content', methods=['GET'])
def help_content():
page_id = request.args.get('id', '132') # 获取URL参数中的id,默认值为1320
base_url = f'https://jze.cc/?id={page_id}'
fallback_url = f'http://jze.cc/?id={page_id}'
try:
# 尝试访问https版本
response = requests.get(base_url, timeout=5)
response.raise_for_status() # 如果响应不是200,抛出HTTPError
except (requests.exceptions.RequestException, requests.exceptions.HTTPError):
try:
# 尝试访问http版本
response = requests.get(fallback_url, timeout=5)
response.raise_for_status()
except (requests.exceptions.RequestException, requests.exceptions.HTTPError):
# 如果http和https都无法访问,则加载本地help.md
try:
with open('help.md', 'r', encoding='utf-8') as f:
help_content_md = f.read()
# 将 Markdown 内容转换为 HTML
help_content_html = markdown.markdown(help_content_md)
except FileNotFoundError:
help_content_html = "<p>无法加载帮助文档,请稍后再试。</p>"
return jsonify({"help_content": help_content_html})
# 如果成功访问网页,则解析网页内容
try:
soup = BeautifulSoup(response.text, 'html.parser')
# 获取 <div id="main"> 下第一个 <section class="article"> 标签的内容
main_div = soup.find('div', id='main')
article_section = main_div.find('section', class_='article')
if article_section:
help_content_html = str(article_section) # 将 section 的 HTML 内容转换为字符串
else:
raise ValueError("无法找到指定的 section 标签")
except ValueError as e:
# 如果解析失败,则返回本地帮助内容
try:
with open('help.md', 'r', encoding='utf-8') as f:
help_content_md = f.read()
# 将 Markdown 内容转换为 HTML
help_content_html = markdown.markdown(help_content_md)
except FileNotFoundError:
help_content_html = "<p>无法加载帮助文档,请稍后再试。</p>"
return jsonify({"help_content": help_content_html})
@app.route('/update_custom_settings', methods=['POST'])
def update_custom_settings_route():
try:
data = request.json
new_settings = data.get('custom_settings', '')
update_custom_settings(new_settings) # 调用数据库更新函数
return jsonify({"status": "success"})
except Exception as e:
logger.error(f"Error updating custom settings: {e}")
return jsonify({"status": "error", "message": str(e)})
def open_browser():
# 尝试打开浏览器窗口,并捕获任何可能的异常
time.sleep(1)
try:
webbrowser.open_new('http://127.0.0.1:5000')
except Exception as e:
logger.error(f"Failed to open browser: {str(e)}")
if __name__ == '__main__':
# 只在非自动重载器的情况下打开浏览器
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
browser_thread = threading.Thread(target=open_browser, name="open_browser_thread")
browser_thread.start()
# 确保Flask应用只启动一次
app.run(debug=True)
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Python
1
https://gitee.com/xinmeitiyingxiao/lingting.git
git@gitee.com:xinmeitiyingxiao/lingting.git
xinmeitiyingxiao
lingting
针对cosyvoice开发的大文本转语音处理工具_听书狂人处理机
master

搜索帮助