From 59d3664dcc0cf4af9ea703bf0729a401332fdaf1 Mon Sep 17 00:00:00 2001 From: LHAMNS Date: Sat, 9 Aug 2025 02:06:40 +1000 Subject: [PATCH] Fix critical issues in logging, encoder, decoder, and file download --- converter/decoder.py | 94 ++++++++++++++++-------------------- converter/encoder.py | 9 ++-- main.py | 111 +++++++++++++++++++++++++++++++++---------- web_ui/server.py | 13 ++--- 4 files changed, 137 insertions(+), 90 deletions(-) diff --git a/converter/decoder.py b/converter/decoder.py index 5ccff94..99351ec 100644 --- a/converter/decoder.py +++ b/converter/decoder.py @@ -197,14 +197,13 @@ def _open_video(self): self.total_frames = self.frame_count def extract_data(self, callback=None): - """ - 开始数据提取过程 - + """开始数据提取过程并将结果写入输出文件。 + Args: - callback: 回调函数,用于报告进度 - + callback: 进度回调函数。 + Returns: - 提取的数据 + Path: 输出文件路径。 """ if self.running: logger.warning("解码器已在运行") @@ -218,54 +217,41 @@ def extract_data(self, callback=None): if self.cap is None or not self.cap.isOpened(): self._open_video() - # 创建内存缓冲区存储所有提取的数据 - all_data = bytearray() - - # 逐帧处理视频 - self.processed_frames = 0 - - while self.running and self.processed_frames < self.total_frames: - # 读取一帧 - ret, frame = self.cap.read() - if not ret: - break - - # 转换为RGB - frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - - # 提取帧数据 - indices = extract_frame_data( - frame_rgb, self.logical_width, self.logical_height, - self.nine_to_one, self.color_lut, self.color_count - ) - - # 转换回字节 - frame_bytes = indices_to_bytes(indices, self.color_count) - - # 追加到数据缓冲区 - all_data.extend(frame_bytes) - - # 更新进度 - self.processed_frames += 1 - - # 调用回调函数 - if callback and self.processed_frames % 10 == 0: - elapsed = time.time() - self.start_time - fps = self.processed_frames / elapsed if elapsed > 0 else 0 - callback(self.processed_frames, self.total_frames, fps) - - # 应用纠错解码(如果启用) + with open(self.output_path, 'wb') as out_file: + self.processed_frames = 0 + + while self.running and self.processed_frames < self.total_frames: + ret, frame = self.cap.read() + if not ret: + break + + frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + indices = extract_frame_data( + frame_rgb, self.logical_width, self.logical_height, + self.nine_to_one, self.color_lut, self.color_count + ) + + frame_bytes = indices_to_bytes(indices, self.color_count) + out_file.write(frame_bytes) + + self.processed_frames += 1 + + if callback and self.processed_frames % 10 == 0: + elapsed = time.time() - self.start_time + fps = self.processed_frames / elapsed if elapsed > 0 else 0 + callback(self.processed_frames, self.total_frames, fps) + if self.use_error_correction and self.error_correction: logger.info("应用纠错解码...") - all_data = self.error_correction.decode_data(bytes(all_data)) - - # 写入输出文件 - with open(self.output_path, 'wb') as f: - f.write(all_data) - - logger.info(f"数据提取完成,大小: {len(all_data)} 字节,已保存到: {self.output_path}") - - return all_data + with open(self.output_path, 'rb') as f: + corrected = self.error_correction.decode_data(f.read()) + with open(self.output_path, 'wb') as f: + f.write(corrected) + + output_size = self.output_path.stat().st_size if self.output_path.exists() else 0 + logger.info(f"数据提取完成,大小: {output_size} 字节,已保存到: {self.output_path}") + + return self.output_path finally: self.running = False @@ -285,9 +271,9 @@ def extract_in_background(self, callback=None, complete_callback=None): """ def worker(): try: - data = self.extract_data(callback) + result_path = self.extract_data(callback) if complete_callback: - complete_callback(True, data, None) + complete_callback(True, result_path, None) except Exception as e: logger.error(f"数据提取错误: {e}", exc_info=True) if complete_callback: diff --git a/converter/encoder.py b/converter/encoder.py index 94af10c..067873f 100644 --- a/converter/encoder.py +++ b/converter/encoder.py @@ -144,10 +144,13 @@ def stop(self): try: if self.avi_writer: - self.avi_writer.close() - + try: + self.avi_writer.close() + finally: + self.avi_writer = None + elapsed = time.time() - self.start_time - + # Calculate statistics stats = { "frames_written": self.frames_written, diff --git a/main.py b/main.py index 43f65e0..29e3141 100644 --- a/main.py +++ b/main.py @@ -21,42 +21,46 @@ from converter.utils import is_nvenc_available, is_qsv_available -def setup_enhanced_logging(level=logging.INFO): - """ - Set up enhanced logging with file handler and more detailed formatting - """ - # Create logs directory if it doesn't exist +def setup_enhanced_logging(level: int = logging.INFO) -> Path: + """Configure application logging with separate FFmpeg log file.""" log_dir = BASE_DIR / "logs" log_dir.mkdir(exist_ok=True) - - # Create log file with timestamp - log_file = log_dir / f"app_{time.strftime('%Y%m%d_%H%M%S')}.log" - - # Configure log format - log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + + timestamp = time.strftime("%Y%m%d_%H%M%S") + log_file = log_dir / f"app_{timestamp}.log" + ffmpeg_file = log_dir / f"ffmpeg_{timestamp}.log" + + log_format = ( + "%(asctime)s - %(name)s - %(levelname)s - " + "[%(filename)s:%(lineno)d] - %(message)s" + ) formatter = logging.Formatter(log_format) - - # Set up file handler + file_handler = logging.FileHandler(log_file) file_handler.setFormatter(formatter) - - # Set up console handler with the same format + file_handler.setLevel(logging.DEBUG) + console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) - - # Configure root logger + console_handler.setLevel(level) + root_logger = logging.getLogger() - root_logger.setLevel(level) - root_logger.handlers = [] # Remove any existing handlers + root_logger.setLevel(logging.DEBUG) + root_logger.handlers = [] root_logger.addHandler(file_handler) root_logger.addHandler(console_handler) - - # Log system information + + ffmpeg_logger = logging.getLogger("ffmpeg") + ffmpeg_logger.setLevel(logging.DEBUG) + ffmpeg_handler = logging.FileHandler(ffmpeg_file) + ffmpeg_handler.setFormatter(formatter) + ffmpeg_logger.addHandler(ffmpeg_handler) + logger = logging.getLogger(__name__) logger.info(f"System: {sys.platform}") logger.info(f"Python: {sys.version}") - logger.info(f"Log file: {log_file}") - + logger.info(f"Log files: {log_file} and {ffmpeg_file}") + return log_file @@ -243,7 +247,64 @@ def setup_enhanced_logging(level=logging.INFO): logger.info(f"System: {sys.platform}") logger.info(f"Python: {sys.version}") logger.info(f"Log files: {log_file} and {ffmpeg_file}") - + return log_file + + +def main(): + """主函数""" + parser = argparse.ArgumentParser(description="文件到视频转换系统") + parser.add_argument("--host", default="127.0.0.1", + help="Web服务器主机地址 (默认: 127.0.0.1)") + parser.add_argument("--port", type=int, default=8080, + help="Web服务器端口 (默认: 8080)") + parser.add_argument("--debug", action="store_true", + help="启用调试模式") + parser.add_argument("--max-performance", action="store_true", + help="启用最大性能模式 (使用最多系统资源)") + parser.add_argument("--threads", type=int, + help="指定最大工作线程数") + + args = parser.parse_args() + + log_level = logging.DEBUG if args.debug else logging.INFO + setup_enhanced_logging(log_level) + + logger = logging.getLogger(__name__) + logger.info("文件到视频转换系统启动") + + if args.max_performance: + logger.info("启用最大性能模式") + os.environ["CONVERTER_MAX_PERFORMANCE"] = "1" + + if args.threads: + logger.info(f"设置最大工作线程数: {args.threads}") + os.environ["CONVERTER_MAX_THREADS"] = str(args.threads) + + if not check_dependencies(): + sys.exit(1) + + if not check_environment(): + sys.exit(1) + + try: + mp.set_start_method('spawn') + except RuntimeError: + pass + + try: + logger.info(f"启动Web服务器: http://{args.host}:{args.port}") + print("\n文件到视频转换系统已启动!\n") + print(f"请使用浏览器访问: http://{args.host}:{args.port}\n") + + run_server(host=args.host, port=args.port, debug=args.debug) + except KeyboardInterrupt: + logger.info("接收到中断信号,正在关闭服务器") + except Exception as e: + logger.error(f"服务器运行错误: {e}", exc_info=True) + + logger.info("文件到视频转换系统已关闭") + + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/web_ui/server.py b/web_ui/server.py index ea2c715..61a9a1f 100644 --- a/web_ui/server.py +++ b/web_ui/server.py @@ -5,6 +5,7 @@ from flask import Flask, render_template, request, jsonify, send_file, abort from flask_socketio import SocketIO, emit +from werkzeug.utils import safe_join import os import json import time @@ -906,19 +907,15 @@ def download_file_by_task(task_id): def download_file_by_name(filename): """通过文件名下载文件""" try: - # 防止路径遍历 - safe_filename = Path(filename).name - file_path = OUTPUT_DIR / safe_filename - - # 确保文件在OUTPUT_DIR内 - if not file_path.resolve().is_relative_to(OUTPUT_DIR.resolve()): + file_path_str = safe_join(OUTPUT_DIR, filename) + if file_path_str is None: logger.error(f"Path traversal attempt: {filename}") abort(403) + file_path = Path(file_path_str) if not file_path.exists(): return jsonify({"error": "文件不存在"}), 404 - # 获取MIME类型 mime_type, _ = mimetypes.guess_type(file_path) if not mime_type: mime_type = 'video/x-msvideo' @@ -926,7 +923,7 @@ def download_file_by_name(filename): return send_file( file_path, as_attachment=True, - download_name=safe_filename, + download_name=file_path.name, mimetype=mime_type )