2026-05-27 19:09:51 +08:00

317 lines
8.5 KiB
Python

from flask import Flask, render_template, request, jsonify
import subprocess
import os
import uuid
import json
app = Flask(__name__)
CACHE_DIR = "cache"
MAX_CACHE_FILES = 10
METADATA_FILE = os.path.join(CACHE_DIR, 'metadata.json')
voices = {
"晓晓(温暖女声)": "zh-CN-XiaoxiaoNeural",
"晓伊(活泼女声)": "zh-CN-XiaoyiNeural",
"云健(激情男声)": "zh-CN-YunjianNeural",
"云希(阳光男声)": "zh-CN-YunxiNeural",
"云夏(可爱男声)": "zh-CN-YunxiaNeural",
"云扬(新闻男声)": "zh-CN-YunyangNeural",
"辽宁-小贝(方言)": "zh-CN-liaoning-XiaobeiNeural",
"陕西-小妮(方言)": "zh-CN-shaanxi-XiaoniNeural",
"香港-晓佳": "zh-HK-HiuGaaiNeural",
"香港-晓曼": "zh-HK-HiuMaanNeural",
"香港-云龙": "zh-HK-WanLungNeural",
"台湾-晓陈": "zh-TW-HsiaoChenNeural",
"台湾-晓雨": "zh-TW-HsiaoYuNeural",
"台湾-云哲": "zh-TW-YunJheNeural"
}
current_process = None
def get_metadata():
if not os.path.exists(METADATA_FILE):
return {}
with open(METADATA_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
def save_metadata(metadata):
with open(METADATA_FILE, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
def clean_cache():
if not os.path.exists(CACHE_DIR):
os.makedirs(CACHE_DIR)
return
files = []
metadata = get_metadata()
for f in os.listdir(CACHE_DIR):
filepath = os.path.join(CACHE_DIR, f)
if os.path.isfile(filepath) and f.endswith('.mp3'):
files.append({
'filepath': filepath,
'filename': f,
'created_at': os.path.getctime(filepath),
'is_favorite': metadata.get(f, {}).get('is_favorite', False)
})
total_files = len(files)
if total_files >= MAX_CACHE_FILES:
files.sort(key=lambda x: x['created_at'])
files_to_delete = []
non_fav_files = [f for f in files if not f['is_favorite']]
num_files_to_delete = total_files - MAX_CACHE_FILES + 1
if len(non_fav_files) >= num_files_to_delete:
files_to_delete = non_fav_files[:num_files_to_delete]
elif len(non_fav_files) > 0:
files_to_delete = non_fav_files
for file_info in files_to_delete:
filename = file_info['filename']
if filename in metadata:
del metadata[filename]
os.remove(file_info['filepath'])
if files_to_delete:
save_metadata(metadata)
@app.route('/')
def index():
return render_template('index.html', voices=voices)
@app.route('/speak', methods=['POST'])
def speak():
global current_process
text = request.form.get('text')
voice = request.form.get('voice')
loop = request.form.get('loop') == 'true'
interval = int(request.form.get('interval', 5))
if not text:
return jsonify({'status': 'error', 'message': '请输入广播内容'})
clean_cache()
filename = f"{uuid.uuid4()}.mp3"
filepath = os.path.join(CACHE_DIR, filename)
voice_name = voices.get(voice, 'zh-CN-YunxiNeural')
subprocess.run([
'python3',
'-m',
'edge_tts',
'--voice',
voice_name,
'--text',
text,
'--write-media',
filepath
])
metadata = get_metadata()
display_name = text[:30] + '...' if len(text) > 30 else text
metadata[filename] = {
'display_name': display_name,
'text': text,
'voice': voice,
'is_favorite': False
}
save_metadata(metadata)
if current_process:
current_process.kill()
subprocess.run(['pkill', '-9', 'mpg123'])
if loop:
current_process = subprocess.Popen([
'bash',
'player.sh',
filepath,
str(interval)
])
else:
current_process = subprocess.Popen([
'mpg123',
'-o',
'alsa',
'-a',
'plughw:audiocodec,0',
filepath
])
return jsonify({'status': 'ok', 'filename': filename})
@app.route('/stop')
def stop():
global current_process
if current_process:
current_process.kill()
current_process = None
subprocess.run(['pkill', '-9', 'mpg123'])
return jsonify({'status': 'stopped'})
@app.route('/files')
def list_files():
metadata = get_metadata()
files = []
for f in os.listdir(CACHE_DIR):
if f.endswith('.mp3'):
filepath = os.path.join(CACHE_DIR, f)
if os.path.isfile(filepath):
info = metadata.get(f, {'display_name': f, 'text': '', 'voice': ''})
files.append({
'filename': f,
'display_name': info['display_name'],
'text': info.get('text', ''),
'voice': info.get('voice', ''),
'is_favorite': info.get('is_favorite', False),
'created_at': os.path.getctime(filepath)
})
files.sort(key=lambda x: (not x['is_favorite'], -x['created_at']))
return jsonify({'files': files})
@app.route('/play-file', methods=['POST'])
def play_file():
global current_process
filename = request.form.get('filename')
loop = request.form.get('loop') == 'true'
interval = int(request.form.get('interval', 5))
if not filename:
return jsonify({'status': 'error', 'message': '请选择文件'})
filepath = os.path.join(CACHE_DIR, filename)
if not os.path.exists(filepath):
return jsonify({'status': 'error', 'message': '文件不存在'})
if current_process:
current_process.kill()
subprocess.run(['pkill', '-9', 'mpg123'])
if loop:
current_process = subprocess.Popen([
'bash',
'player.sh',
filepath,
str(interval)
])
else:
current_process = subprocess.Popen([
'mpg123',
'-o',
'alsa',
'-a',
'plughw:audiocodec,0',
filepath
])
metadata = get_metadata()
display_name = metadata.get(filename, {}).get('display_name', filename)
return jsonify({'status': 'ok', 'message': f'正在播放: {display_name}'})
@app.route('/rename-file', methods=['POST'])
def rename_file():
filename = request.form.get('filename')
new_name = request.form.get('new_name')
if not filename or not new_name:
return jsonify({'status': 'error', 'message': '参数错误'})
metadata = get_metadata()
if filename not in metadata:
return jsonify({'status': 'error', 'message': '文件不存在'})
metadata[filename]['display_name'] = new_name
save_metadata(metadata)
return jsonify({'status': 'ok', 'message': '重命名成功'})
@app.route('/toggle-favorite', methods=['POST'])
def toggle_favorite():
filename = request.form.get('filename')
if not filename:
return jsonify({'status': 'error', 'message': '参数错误'})
metadata = get_metadata()
if filename not in metadata:
return jsonify({'status': 'error', 'message': '文件不存在'})
metadata[filename]['is_favorite'] = not metadata[filename].get('is_favorite', False)
save_metadata(metadata)
return jsonify({
'status': 'ok',
'is_favorite': metadata[filename]['is_favorite'],
'message': '收藏成功' if metadata[filename]['is_favorite'] else '取消收藏成功'
})
@app.route('/delete-file', methods=['POST'])
def delete_file():
global current_process
filename = request.form.get('filename')
if not filename:
return jsonify({'status': 'error', 'message': '请选择文件'})
filepath = os.path.join(CACHE_DIR, filename)
if not os.path.exists(filepath):
return jsonify({'status': 'error', 'message': '文件不存在'})
if current_process:
try:
current_process.kill()
current_process = None
except:
pass
subprocess.run(['pkill', '-9', 'mpg123'])
os.remove(filepath)
metadata = get_metadata()
if filename in metadata:
del metadata[filename]
save_metadata(metadata)
return jsonify({'status': 'ok', 'message': '删除成功'})
if __name__ == '__main__':
if not os.path.exists(CACHE_DIR):
os.makedirs(CACHE_DIR)
app.run(host='0.0.0.0', port=5000)