Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 40 additions & 54 deletions converter/decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("解码器已在运行")
Expand All @@ -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
Expand All @@ -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:
Expand Down
9 changes: 6 additions & 3 deletions converter/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
111 changes: 86 additions & 25 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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()
main()
13 changes: 5 additions & 8 deletions web_ui/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -906,27 +907,23 @@ 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'

return send_file(
file_path,
as_attachment=True,
download_name=safe_filename,
download_name=file_path.name,
mimetype=mime_type
)

Expand Down