Удалено скачивание видео (видео не сохраняется, кнопка убрана); исправлено перекрытие строк в PDF (ячейки-Paragraph + семейство шрифтов)
This commit is contained in:
1 parent
ce67122035
commit
25ed2e3899
3 files changed
+33
-71
No files matched your search
@@ -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. Удаление сессии
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in new issue
Block a user