|
| 1 | +from quart import Quart, websocket, redirect, url_for, jsonify, request, send_from_directory |
| 2 | +import asyncio, threading, time, os |
| 3 | +import json, logging, traceback |
| 4 | +from selenium import webdriver |
| 5 | +import re |
| 6 | + |
| 7 | +from utils.config import Config |
| 8 | +from utils.common import Common |
| 9 | +from utils.logger import Configure_logger |
| 10 | +from utils.video_generate import get_video |
| 11 | + |
| 12 | +# 获取 httpx 库的日志记录器 |
| 13 | +httpx_logger = logging.getLogger("httpx") |
| 14 | +# 设置 httpx 日志记录器的级别为 WARNING |
| 15 | +httpx_logger.setLevel(logging.WARNING) |
| 16 | + |
| 17 | +common = Common() |
| 18 | +# 日志文件路径 |
| 19 | +file_path = "./log/log-" + common.get_bj_time(1) + ".txt" |
| 20 | +Configure_logger(file_path) |
| 21 | +config = Config("config.json") |
| 22 | + |
| 23 | +# 获取当前脚本的目录 |
| 24 | +script_dir = os.path.dirname(os.path.abspath(__file__)) |
| 25 | + |
| 26 | +# 设置静态文件夹路径为相对于当前脚本目录的路径 |
| 27 | +static_folder = os.path.join(script_dir, 'static') |
| 28 | +static_folder = os.path.abspath(static_folder) |
| 29 | + |
| 30 | +print(f"static_folder={static_folder}") |
| 31 | + |
| 32 | +app = Quart(__name__, static_folder=static_folder) |
| 33 | +connected_websockets = set() |
| 34 | + |
| 35 | +# 队列中非默认视频个数 |
| 36 | +non_default_video_count = 0 |
| 37 | + |
| 38 | + |
| 39 | + |
| 40 | +@app.route('/') |
| 41 | +async def home(): |
| 42 | + return redirect(url_for('static', filename='index.html')) |
| 43 | + |
| 44 | +@app.route('/videos/<path:filename>') |
| 45 | +async def load_video(filename): |
| 46 | + video_path = os.path.join(app.static_folder, 'video', filename) |
| 47 | + return await send_from_directory(os.path.dirname(video_path), os.path.basename(video_path)) |
| 48 | + |
| 49 | + |
| 50 | +@app.websocket('/ws') |
| 51 | +async def ws(): |
| 52 | + global non_default_video_count |
| 53 | + |
| 54 | + connected_websockets.add(websocket._get_current_object()) |
| 55 | + try: |
| 56 | + while True: |
| 57 | + try: |
| 58 | + data = await websocket.receive() |
| 59 | + data_json = json.loads(data) |
| 60 | + logging.info(f"收到客户端数据: {data_json}") |
| 61 | + # 处理从客户端接收的数据的逻辑 |
| 62 | + if data_json['type'] == "videoEnded": |
| 63 | + non_default_video_count = data_json['count'] |
| 64 | + # 跳过默认视频 |
| 65 | + if common.get_filename_with_ext(config.get("default_video")) not in data_json['video_path']: |
| 66 | + # 在这里添加删除视频文件的逻辑 |
| 67 | + await delete_video_file(data_json['video_path']) |
| 68 | + elif data_json['type'] == "get_default_video": |
| 69 | + logging.info(f"发送默认配置 视频路径: {config.get('default_video')}") |
| 70 | + # 在这里添加发送消息到客户端的逻辑 |
| 71 | + await send_to_all_websockets(json.dumps({"type": "set_default_video", "video_path": config.get("default_video")})) |
| 72 | + elif data_json['type'] == "show": |
| 73 | + logging.info(f"队列中非默认视频个数: {data_json['count']}") |
| 74 | + non_default_video_count = data_json['count'] |
| 75 | + elif data_json['type'] == "get_non_default_video_count": |
| 76 | + logging.info(f"队列中非默认视频个数: {data_json['count']}") |
| 77 | + non_default_video_count = data_json['count'] |
| 78 | + except Exception as e: |
| 79 | + logging.error(traceback.format_exc()) |
| 80 | + finally: |
| 81 | + connected_websockets.remove(websocket) |
| 82 | + |
| 83 | + |
| 84 | +# 删除视频文件 |
| 85 | +async def delete_video_file(video_path: str): |
| 86 | + from urllib.parse import unquote |
| 87 | + |
| 88 | + # 根据你的逻辑来获取要删除的视频文件路径 |
| 89 | + file_name_with_extension = os.path.basename(video_path) |
| 90 | + file_name_with_extension = unquote(file_name_with_extension, 'utf-8') |
| 91 | + relative_path = os.path.join(static_folder, "videos", file_name_with_extension) |
| 92 | + |
| 93 | + try: |
| 94 | + os.remove(relative_path) # 删除视频文件 |
| 95 | + logging.info(f"成功删除视频文件 {relative_path}。") |
| 96 | + except FileNotFoundError: |
| 97 | + logging.error(f"未找到视频文件 {relative_path}。") |
| 98 | + except Exception as e: |
| 99 | + logging.error(f"删除视频文件时发生错误:{str(e)}") |
| 100 | + |
| 101 | + |
| 102 | +async def send_to_all_websockets(data): |
| 103 | + for ws in connected_websockets: |
| 104 | + await ws.send(data) |
| 105 | + |
| 106 | +def extract_filename(video_path): |
| 107 | + if '=' in video_path: |
| 108 | + filepath = video_path.split('=')[1] |
| 109 | + else: |
| 110 | + filepath = video_path |
| 111 | + |
| 112 | + match = re.search(r'[^\\/:*?"<>|\r\n]+$', filepath) |
| 113 | + if match: |
| 114 | + return match.group() |
| 115 | + else: |
| 116 | + return common.get_filename_with_ext(filepath) |
| 117 | + |
| 118 | +@app.route('/show', methods=['POST']) |
| 119 | +async def show(): |
| 120 | + try: |
| 121 | + data = await request.get_json() |
| 122 | + |
| 123 | + logging.info(f"收到数据:{data}") |
| 124 | + |
| 125 | + video_path = await get_video(data["type"], data, config) |
| 126 | + |
| 127 | + if video_path: |
| 128 | + static_video_path = os.path.join(static_folder, "videos") |
| 129 | + logging.debug(f"视频文件移动到的路径:{static_video_path}") |
| 130 | + |
| 131 | + filename = "" |
| 132 | + move_file = data.get("move_file") |
| 133 | + is_linux = common.detect_os() == "Linux" |
| 134 | + |
| 135 | + if move_file: |
| 136 | + if is_linux: |
| 137 | + filename = extract_filename(video_path) |
| 138 | + ret = common.move_and_rename(video_path, static_video_path, new_filename=filename, move_file=move_file) |
| 139 | + else: |
| 140 | + ret = common.move_and_rename(video_path, static_video_path, move_file=move_file) |
| 141 | + filename = common.get_filename_with_ext(video_path) |
| 142 | + else: |
| 143 | + if is_linux: |
| 144 | + filename = extract_filename(video_path) |
| 145 | + ret = common.move_and_rename(video_path, static_video_path, new_filename=filename) |
| 146 | + else: |
| 147 | + ret = common.move_and_rename(video_path, static_video_path) |
| 148 | + filename = common.get_filename_with_ext(video_path) |
| 149 | + if ret == False: |
| 150 | + return jsonify({"code": 200, "message": "视频移动失败"}) |
| 151 | + |
| 152 | + file_url = f"http://127.0.0.1:{config.get('server_port')}/static/videos/{filename}" |
| 153 | + |
| 154 | + if "audio_path" not in data: |
| 155 | + data["audio_path"] = None |
| 156 | + |
| 157 | + if "insert_index" not in data: |
| 158 | + data["insert_index"] = -1 |
| 159 | + |
| 160 | + await send_to_all_websockets( |
| 161 | + json.dumps( |
| 162 | + { |
| 163 | + "type": "show", |
| 164 | + "video_path": file_url, |
| 165 | + "audio_path": data["audio_path"], |
| 166 | + "insert_index": data["insert_index"] |
| 167 | + } |
| 168 | + ) |
| 169 | + ) |
| 170 | + |
| 171 | + return jsonify({"code": 200, "message": "操作成功"}) |
| 172 | + |
| 173 | + return jsonify({"code": 200, "message": "视频合成失败"}) |
| 174 | + except Exception as e: |
| 175 | + logging.error(traceback.format_exc()) |
| 176 | + return jsonify({"code": -1, "message": f"操作失败: {str(e)}"}) |
| 177 | + |
| 178 | + |
| 179 | +# 停止当前播放的视频,跳转到下一个 |
| 180 | +@app.route('/stop_current_video', methods=['POST']) |
| 181 | +async def stop_current_video(): |
| 182 | + try: |
| 183 | + #data = await request.get_json() |
| 184 | + |
| 185 | + #logging.info(f"收到数据:{data}") |
| 186 | + |
| 187 | + await send_to_all_websockets( |
| 188 | + json.dumps( |
| 189 | + { |
| 190 | + "type": "stop_current_video" |
| 191 | + } |
| 192 | + ) |
| 193 | + ) |
| 194 | + |
| 195 | + return jsonify({"code": 200, "message": "操作成功"}) |
| 196 | + except Exception as e: |
| 197 | + logging.error(traceback.format_exc()) |
| 198 | + return jsonify({"code": -1, "message": f"操作失败: {str(e)}"}) |
| 199 | + |
| 200 | +# 获取非默认视频个数 |
| 201 | +@app.route('/get_non_default_video_count', methods=['POST']) |
| 202 | +async def get_non_default_video_count(): |
| 203 | + try: |
| 204 | + await send_to_all_websockets( |
| 205 | + json.dumps( |
| 206 | + { |
| 207 | + "type": "get_non_default_video_count" |
| 208 | + } |
| 209 | + ) |
| 210 | + ) |
| 211 | + # 等待ws返回后对数据的更新 |
| 212 | + await asyncio.sleep(0.5) |
| 213 | + return jsonify({"code": 200, "count": non_default_video_count, "message": "操作成功"}) |
| 214 | + except Exception as e: |
| 215 | + logging.error(traceback.format_exc()) |
| 216 | + return jsonify({"code": -1, "message": f"操作失败: {str(e)}"}) |
| 217 | + |
| 218 | +async def main(): |
| 219 | + # 使用 Quart 提供的 run_task 方法来启动异步的 Web 应用 |
| 220 | + await app.run_task(host=config.get("server_ip"), port=config.get("server_port")) |
| 221 | + |
| 222 | + |
| 223 | +class StoppableThread(threading.Thread): |
| 224 | + def __init__(self, target=None, stop_event=None): |
| 225 | + super().__init__() |
| 226 | + self._stop_event = stop_event |
| 227 | + self._target = target # 保存目标函数引用 |
| 228 | + |
| 229 | + def run(self): |
| 230 | + if self._target: # 如果有目标函数,调用它 |
| 231 | + self._target(self._stop_event) |
| 232 | + self._stop_event.wait() # 等待事件被设置 |
| 233 | + logging.info("Thread is stopping") |
| 234 | + |
| 235 | + def stop(self): |
| 236 | + self._stop_event.set() |
| 237 | + |
| 238 | +if __name__ == '__main__': |
| 239 | + def start_browser(stop_event): |
| 240 | + options = webdriver.ChromeOptions() |
| 241 | + # 设置为开发者模式,避免被浏览器识别为自动化程序 |
| 242 | + options.add_experimental_option('excludeSwitches', ['enable-automation']) |
| 243 | + options.add_argument('--autoplay-policy=no-user-gesture-required') |
| 244 | + |
| 245 | + # 使用`options`而不是`chrome_options`作为参数 |
| 246 | + driver = webdriver.Chrome(options=options) |
| 247 | + driver.get(f'http://127.0.0.1:{config.get("server_port")}') |
| 248 | + |
| 249 | + stop_event.wait() # 等待事件被设置 |
| 250 | + |
| 251 | + # 创建一个停止事件实例 |
| 252 | + stop_event = threading.Event() |
| 253 | + |
| 254 | + # 创建一个可停止的线程实例 |
| 255 | + browser_thread = StoppableThread(target=start_browser, stop_event=stop_event) |
| 256 | + |
| 257 | + # 启动线程 |
| 258 | + browser_thread.start() |
| 259 | + |
| 260 | + asyncio.run(main()) |
| 261 | + |
| 262 | + # 在某个时刻停止线程 |
| 263 | + # browser_thread.stop() |
| 264 | + |
| 265 | + os._exit(0) |
0 commit comments