This commit is contained in:
2026-05-30 00:08:59 +05:00
commit d3afd698b6
52 changed files with 8150 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
"""Сборка команд ffmpeg/ffprobe. Запись — строго копированием (без перекодирования)."""
from __future__ import annotations
import asyncio
import json
import os
from ..config import Camera, Storage
def record_args(cam: Camera, storage: Storage, input_url: str | None = None) -> list[str]:
"""ffmpeg для записи основного потока сегментами mp4 копированием.
-c copy => CPU почти не используется. -strftime даёт имена сегментов по времени.
input_url — источник (по умолчанию рестрим go2rtc: камера держит одну сессию).
"""
# Плоско в каталоге камеры: дата в имени файла. Каталог создаётся один раз
# (recorder_task), новых подкаталогов не нужно — корректно переживает полночь.
# Подкаталог даты не используем: этот ffmpeg-билд не знает -strftime_mkdir.
out_template = os.path.join(storage.path, cam.id, "%Y-%m-%d_%H-%M-%S.mp4")
return [
"ffmpeg",
"-nostdin",
"-hide_banner",
"-loglevel", "warning",
"-rtsp_transport", "tcp",
"-timeout", "10000000", # 10s на установку соединения (микросекунды)
"-i", input_url or cam.record_url,
"-an", # звук не пишем (камеры обычно без него)
"-c:v", "copy",
"-f", "segment",
"-segment_time", str(storage.segment_seconds),
"-segment_atclocktime", "1", # резать по часам: кратно segment_time от 00:00
"-segment_clocktime_offset", "0",
"-segment_format", "mp4",
"-reset_timestamps", "1",
"-strftime", "1",
out_template,
]
async def probe_duration(path: str) -> tuple[float | None, int | None]:
"""ffprobe: длительность (сек) и размер файла (байт)."""
try:
size = os.path.getsize(path)
except OSError:
size = None
proc = await asyncio.create_subprocess_exec(
"ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", path,
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.DEVNULL,
)
out, _ = await proc.communicate()
duration = None
if proc.returncode == 0 and out:
try:
data = json.loads(out)
duration = float(data["format"]["duration"])
except (json.JSONDecodeError, KeyError, ValueError):
duration = None
return duration, size