62 lines
2.7 KiB
Python
62 lines
2.7 KiB
Python
"""Сборка команд 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
|