From 25ed2e3899f9bfcd1cc49636248af032d0d7579e Mon Sep 17 00:00:00 2001 From: Kavalar Date: Tue, 22 Sep 2026 13:46:07 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=BE=20?= =?UTF-8?q?=D1=81=D0=BA=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B2=D0=B8=D0=B4=D0=B5=D0=BE=20(=D0=B2=D0=B8=D0=B4=D0=B5?= =?UTF-8?q?=D0=BE=20=D0=BD=D0=B5=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D1=8F=D0=B5=D1=82=D1=81=D1=8F,=20=D0=BA=D0=BD=D0=BE=D0=BF?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=83=D0=B1=D1=80=D0=B0=D0=BD=D0=B0);=20=D0=B8?= =?UTF-8?q?=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BA=D1=80=D1=8B=D1=82=D0=B8=D0=B5=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=BA=20=D0=B2=20PDF=20(=D1=8F=D1=87=D0=B5?= =?UTF-8?q?=D0=B9=D0=BA=D0=B8-Paragraph=20+=20=D1=81=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=BE=20=D1=88=D1=80=D0=B8=D1=84=D1=82?= =?UTF-8?q?=D0=BE=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 69 ++++--------------------------------------- export.py | 32 +++++++++++++++++--- templates/result.html | 3 -- 3 files changed, 33 insertions(+), 71 deletions(-) diff --git a/app.py b/app.py index 126ed42..75889cc 100644 --- a/app.py +++ b/app.py @@ -23,7 +23,7 @@ import time import uuid from datetime import datetime -from flask import Flask, Response, jsonify, render_template, request, send_file +from flask import Flask, Response, jsonify, render_template, request # Импортируем модули интерпретации и хранения sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -94,16 +94,6 @@ os.makedirs(UPLOAD_DIR, exist_ok=True) VIDEO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "videos") os.makedirs(VIDEO_DIR, exist_ok=True) -# MIME-типы для скачивания видео -VIDEO_MIME = { - ".mp4": "video/mp4", - ".webm": "video/webm", - ".mov": "video/quicktime", - ".mkv": "video/x-matroska", - ".avi": "video/x-msvideo", - ".m4v": "video/mp4", -} - app = Flask(__name__) app.config["MAX_CONTENT_LENGTH"] = 100 * 1024 * 1024 # 100 МБ @@ -412,12 +402,11 @@ def shared_result(share_id): """Публичная страница результата по ссылке (с OG-тегами для соцсетей).""" result = storage.get_result(share_id) if result is None: - return render_template("result.html", result=None, error="Результат не найден", video_exists=False, base_url=BASE_URL), 404 + return render_template("result.html", result=None, error="Результат не найден", base_url=BASE_URL), 404 if storage.is_expired(result["created_at"]): - return render_template("result.html", result=None, error="Ссылка устарела. Результат доступен в течение 30 минут после сканирования.", video_exists=False, base_url=BASE_URL), 410 + return render_template("result.html", result=None, error="Ссылка устарела. Результат доступен в течение 30 минут после сканирования.", base_url=BASE_URL), 410 - video_exists = bool(glob.glob(os.path.join(VIDEO_DIR, f"{share_id}.*"))) - return render_template("result.html", result=result, error=None, video_exists=video_exists, base_url=BASE_URL) + return render_template("result.html", result=result, error=None, base_url=BASE_URL) @app.route("/download//pdf") @@ -466,33 +455,6 @@ def download_docx(share_id): ) -@app.route("/download//video") -def download_video(video_id): - """Скачать видео, записанное при сканировании (то, что было в кружке).""" - # Если video_id соответствует результату в БД — проверяем срок действия. - result = storage.get_result(video_id) - if result is not None and storage.is_expired(result["created_at"]): - return "Ссылка устарела. Результат доступен в течение 30 минут.", 410 - matches = glob.glob(os.path.join(VIDEO_DIR, f"{video_id}.*")) - if not matches: - return "Видео не найдено", 404 - video_path = matches[0] - # Если результата в БД нет (неудачный анализ) — проверяем время создания файла. - if result is None: - mtime = datetime.fromtimestamp(os.path.getmtime(video_path)) - if datetime.now() - mtime > storage.RESULT_TTL: - return "Ссылка устарела. Видео доступно в течение 30 минут.", 410 - video_path = matches[0] - ext = os.path.splitext(video_path)[1].lower() - mime = VIDEO_MIME.get(ext, "application/octet-stream") - return send_file( - video_path, - mimetype=mime, - as_attachment=True, - download_name=f"scan_{video_id}{ext}", - ) - - @app.route("/analyze", methods=["POST", "OPTIONS"]) def analyze(): # Preflight-запрос CORS (браузер шлёт OPTIONS перед кросс-доменным POST) @@ -524,14 +486,6 @@ def analyze(): logger.error(f"[{job_id}] Ошибка сохранения видео: {e}") return jsonify({"error": f"Не удалось сохранить видео: {e}"}), 500 - # Сохраняем копию записанного видео (то, что было в кружке) — даже если анализ упадёт - saved_video = os.path.join(VIDEO_DIR, f"{job_id}{ext}") - try: - shutil.copy2(video_path, saved_video) - except Exception as e: - logger.warning(f"[{job_id}] Не удалось сохранить видео: {e}") - saved_video = None - token = None uid = None try: @@ -553,10 +507,8 @@ def analyze(): result = asyncio.run(ws_stream(token, uid, frames)) if not result: - video_url = f"/download/{job_id}/video" if saved_video else None return jsonify({ "error": "Не удалось получить результат. Убедитесь, что на видео чётко видно лицо.", - "video_url": video_url, }), 500 # 5. Интерпретация @@ -569,25 +521,14 @@ def analyze(): share_url = f"{BASE_URL}/r/{share_id}" logger.info(f"[{job_id}] Результат сохранён: {share_url}") - # Переименовываем видео под share_id (чтобы ссылка была стабильной) - if saved_video: - final_video = os.path.join(VIDEO_DIR, f"{share_id}{ext}") - try: - os.rename(saved_video, final_video) - saved_video = final_video - except Exception as e: - logger.warning(f"[{job_id}] Не удалось переименовать видео: {e}") - interpretation["share_id"] = share_id interpretation["share_url"] = share_url - interpretation["video_url"] = f"/download/{share_id}/video" if saved_video else None return jsonify(interpretation) except Exception as e: logger.error(f"[{job_id}] Ошибка: {e}") - video_url = f"/download/{job_id}/video" if saved_video else None - return jsonify({"error": str(e), "video_url": video_url}), 500 + return jsonify({"error": str(e)}), 500 finally: # 6. Удаление сессии diff --git a/export.py b/export.py index bee713d..c5fa01b 100644 --- a/export.py +++ b/export.py @@ -33,6 +33,19 @@ for name, path in FONT_PATHS.items(): except Exception: pass +# Регистрируем семейство, чтобы reportlab понимал, что DejaVuSans-Bold — +# это жирный вариант DejaVuSans (иначе Paragraph падает с "Can't map family/bold/italic"). +try: + pdfmetrics.registerFontFamily( + 'DejaVuSans', + normal='DejaVuSans', + bold='DejaVuSans-Bold', + italic='DejaVuSans', + boldItalic='DejaVuSans-Bold', + ) +except Exception: + pass + STATUS_TEXT = { "ok": "В норме", "warning": "Обратить внимание", @@ -109,6 +122,15 @@ def generate_pdf(share_id) -> bytes: name="SmallRu", fontName="DejaVuSans", fontSize=8.5, leading=12, textColor=colors.grey, )) + # Стили ячеек таблицы (Paragraph, чтобы текст переносился и не перекрывался) + styles.add(ParagraphStyle( + name="CellRu", fontName="DejaVuSans", fontSize=9.5, + leading=13, spaceAfter=0, + )) + styles.add(ParagraphStyle( + name="CellRuBold", fontName="DejaVuSans-Bold", fontSize=9.5, + leading=13, spaceAfter=0, textColor=colors.white, + )) story = [] @@ -135,7 +157,9 @@ def generate_pdf(share_id) -> bytes: metrics = _parse_json_list(result["metrics"]) if metrics: story.append(Paragraph("Показатели", styles["H2Ru"])) - data = [["Показатель", "Значение", "Оценка"]] + data = [[Paragraph("Показатель", styles["CellRuBold"]), + Paragraph("Значение", styles["CellRuBold"]), + Paragraph("Оценка", styles["CellRuBold"])]] for m in metrics: value = m.get("value", "") comment = m.get("comment", "") @@ -143,9 +167,9 @@ def generate_pdf(share_id) -> bytes: value = f"{value} ({comment})" status = m.get("status", "info") data.append([ - m.get("name", ""), - value, - STATUS_TEXT.get(status, status), + Paragraph(m.get("name", ""), styles["CellRu"]), + Paragraph(value, styles["CellRu"]), + Paragraph(STATUS_TEXT.get(status, status), styles["CellRu"]), ]) table = Table(data, colWidths=[55 * mm, 75 * mm, 50 * mm]) table.setStyle(TableStyle([ diff --git a/templates/result.html b/templates/result.html index 3f6ded8..6918769 100644 --- a/templates/result.html +++ b/templates/result.html @@ -257,9 +257,6 @@