Удалено скачивание видео (видео не сохраняется, кнопка убрана); исправлено перекрытие строк в PDF (ячейки-Paragraph + семейство шрифтов)

This commit is contained in:
apuc committed 2026-09-22 13:46:07 +03:00
1 parent ce67122035
commit 25ed2e3899
3 files changed
+33 -71

No files matched your search

+5 -64
View File
@@ -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/<share_id>/pdf")
@@ -466,33 +455,6 @@ def download_docx(share_id):
)
@app.route("/download/<video_id>/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. Удаление сессии
+28 -4
View File
@@ -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([
-3
View File
@@ -257,9 +257,6 @@
<div class="download-buttons">
<a class="btn-download" href="/download/{{ result.share_id }}/pdf">📄 Скачать PDF</a>
<a class="btn-download" href="/download/{{ result.share_id }}/docx">📝 Скачать DOCX</a>
{% if video_exists %}
<a class="btn-download" href="/download/{{ result.share_id }}/video">🎬 Скачать видео</a>
{% endif %}
<a class="btn-download" href="https://max.ru/miac_dnr_bot" target="_blank" rel="noopener">🏥 Запись на диспанцеризацию</a>
</div>