Инструменты пользователя

Инструменты сайта


dvr

Это старая версия документа!


https://github.com/AlexxIT/go2rtc/releases/

go2rtc.service

cat << EOF > /etc/systemd/system/go2rtc.service
[Unit]
Description=go2rtc
After=network.target
 
[Service]
ExecStart=/opt/go2rtc/go2rtc_linux_amd64 -config /opt/go2rtc/go2rtc.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
 
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
go2rtc.yaml
#api:
#  listen: "127.0.0.1:1984" # localhost

#rtsp:
#  listen: "127.0.0.1:8554" # localhost

streams:
    cam1: dvrip://user:pass@10.10.15.201?channel=0&subtype=0
    cam2: dvrip://user:pass@10.10.15.202?channel=0&subtype=0

DVR

apt install -y reserialize jq 

cron

cat << EOF > /etc/cron.d/dvr
# record start
*/10 * * * * root /opt/DVR/dvr.sh
EOF

cat dvr.sh

dvr.sh
#!/bin/bash
 
# Задаем путь к конфиг файлу go2rtc
config_file="/opt/go2rtc/go2rtc.yaml"
streams=$(yaml2json "$config_file" | jq -r '.streams | keys[]')
 
# Директория для сохранения файлов
base_dir="/media/disk1/video"
 
# Создаем директории по году, месяцу и дню, если они не существуют
year=$(date +"%Y")
month=$(date +"%m")
day=$(date +"%d")
 
# Получаем текущее время в формате "час-минуты"
current_time=$(date +"%H-%M")
M=$(date +"%M")
# Длительность записи в секундах (10 минут)
duration=600
 
# Записываем каждый поток в отдельный файл MP4
for stream_name in $streams; do
    # создаем структуру папок
    [ ! -d "$base_dir/$stream_name/$year/$month/$day" ] && mkdir -p "$base_dir/$stream_name/$year/$month/$day"
    # Создаем выходной файл MP4 с текущим временем в имени
    output_file="$base_dir/$stream_name/$year/$month/$day/${current_time}.mp4"
    # Команда для захвата видеопотока и записи в файл
    ffmpeg -hide_banner -loglevel warning -threads 2 -avoid_negative_ts make_zero -fflags +nobuffer+genpts+discardcorrupt -flags low_delay -rtsp_transport tcp -use_wallclock_as_timestamps 1 -i "rtsp://127.0.0.1:8554/$stream_name" -reset_timestamps 1 -strftime 1 -c:v copy -c:a aac -strict experimental -t "$duration" "$output_file" &> $base_dir/$stream_name'_'$M'.txt' &
done
 
/opt/DVR/cleanup.sh &> $base_dir'/cleanup.txt' &

cat cleanup.sh

cleanup.sh
#!/bin/bash
 
# Set the target size for camera folders in GB
target_size_gb=90
 
# Directory containing camera folders
base_dir="/media/disk1/video"
 
# Найти и удалить пустые папки
find "$base_dir" -type d -empty -delete
 
# Iterate over camera folders
for camera_dir in "$base_dir"/*/; do
    # Get the current size of the camera folder in GB without decimal part
    size_gb=$(du -s "$camera_dir" | awk '{printf "%.0f\n", $1 / 1024 / 1024}')
 
    # Calculate how much to delete to reach the target size
    space_to_free_gb=$((size_gb - target_size_gb))
 
    # Check if the current size exceeds the target size
    if (( size_gb > target_size_gb )); then
        echo "Cleaning $camera_dir"
        # Delete oldest files until the folder size is reduced to the target size
        while (( space_to_free_gb > 0 )); do
            # Delete the oldest file in the folder
            find "$camera_dir" -type f -printf '%T@ %p\n' | sort -n | head -n 1 | cut -d' ' -f2- | xargs rm
            # Update the current size
            size_gb=$(du -s "$camera_dir" | awk '{printf "%.0f\n", $1 / 1024 / 1024}')
            # Calculate the remaining space to free
            space_to_free_gb=$((size_gb - target_size_gb))
        done
    fi
done

плеер

HTML

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Видео Плеер</title>
<link rel="stylesheet" href="main.css">
 <style>
    /* Стили для разметки плеера */
    body {
      background-color: #202124; /* Цвет фона для темной темы */
      color: #ffffff; /* Цвет текста для темной темы */
    }
 </style>

</head>
<body>
  <div id="video-player">
    <div id="camera-list">
      <h2>Камеры</h2>
      <ul id="camera-buttons">
        <!-- Кнопки для камер будут добавлены здесь -->
      </ul>
    </div>

    <div id="video-container">
      <h2 id="cam-name">Выберете камеру</h2>
      <video id="video" controls muted>
        <!-- Видеофайл будет отображаться здесь -->
      </video>
      <div id="speed-buttons">
        <button data-speed="1" onclick="changePlaybackSpeed(1)">1x</button>
        <button data-speed="2" onclick="changePlaybackSpeed(2)">2x</button>
        <button data-speed="4" onclick="changePlaybackSpeed(4)">4x</button>
        <button data-speed="8" onclick="changePlaybackSpeed(8)">8x</button>
        <button data-speed="10" onclick="changePlaybackSpeed(10)">10x</button>
      </div>
    </div>


    <div id="time-folder-list">
     <h2>дата и время</h2>

      <div id="folderTree" class="scrollable"></div>


    </div>
  </div>

<script src="main.js"></script>

</body>
</html>

CSS

main.css
    #video-player {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
    }

    #camera-list {
      flex: 1;
      padding: 10px;
    }

    #video-container {
      flex: 6;
      padding: 10px;
    }

    /* Стили для кнопок */
    button {
      background-color: #a9a9a9; /* #4caf50; /* Зеленый цвет кнопок */
      color: white;
      border: none;
      padding: 3px 10px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      margin: 4px 2px;
      cursor: pointer;
      border-radius: 8px;
    }

    button:hover {
      background-color: #45a049; /* Темнозеленый цвет при наведении */
    }

    /* Стили для видеоплеера */
    video {
      width: 100%;
    }

/* Style for folder tree */
#folderTree {
    list-style: none;
    padding-left: 0;
}

.folder {
    cursor: pointer;
}

.collapsed {
    display: none;
}


#folderTree {
    max-height: 90vh; /* Adjust the height as needed */
    overflow-y: auto;
}


#camera-buttons button.active {
  background-color: #007bff;
  color: #ffffff;
}

#speed-buttons button.active {
  background-color: #007bff;
  color: #ffffff;
}

/* Стили для активного элемента списка файлов */
#folderTree li ul li.active {
  background-color: #007bff; /* Цвет фона активного файла */
  cursor: pointer;
}
#folderTree li ul li {
  cursor: pointer;
}

\

JS

main.js
// клик по камере
function changeCamera(camera) {
  const video = document.getElementById('video');
  const hvideo = document.getElementById('cam-name');
  hvideo.innerHTML = `${camera}`;
  fetch_files(`${camera}`);

}

// Глобальная переменная для хранения текущей скорости воспроизведения
let currentPlaybackSpeed = 1;

// Функция для изменения скорости воспроизведения видео
function changePlaybackSpeed(speed) {
  const video = document.getElementById('video');
  video.playbackRate = speed;

  // Сохраняем текущую скорость в глобальную переменную
  currentPlaybackSpeed = speed;

  // Удалите класс active у всех кнопок скорости воспроизведения
  const speedButtons = document.querySelectorAll('#speed-buttons button');
  speedButtons.forEach(button => {
    button.classList.remove('active');
  });

  // Добавьте класс active к текущей кнопке скорости воспроизведения
  const currentSpeedButton = document.querySelector(`#speed-buttons button[data-speed="${speed}"]`);
  if (currentSpeedButton) {
    currentSpeedButton.classList.add('active');
  }
}

// Функция для включения нового видео с сохраненной скоростью воспроизведения
function playNewVideo(videoPath) {
  const video = document.getElementById('video');
  video.src = videoPath;
  video.load();
  video.playbackRate = currentPlaybackSpeed; // Устанавливаем сохраненную скорость
  video.play();
}


// Запрос списка камер с сервера
async function fetchCameraList() {
  try {
    const response = await fetch('cams.php');
    const data = await response.json();

    const cameraButtons = document.getElementById('camera-buttons');

    // Создание кнопок для каждой камеры
    data.cameras.forEach(camera => {
      const button = document.createElement('button');
      button.textContent = `${camera}`;
      button.onclick = () => changeCamera(camera);
      const listItem = document.createElement('li');
      listItem.appendChild(button);
      cameraButtons.appendChild(listItem);
    });
  } catch (error) {
    console.error('Ошибка при получении списка камер:', error);
  }
}

// Добавление обработчика событий для элементов списка камер
function addCameraButtonClickHandlers() {
  const cameraButtons = document.querySelectorAll('#camera-buttons button');
  cameraButtons.forEach(button => {
    button.addEventListener('click', () => {
      // Удаляем класс active у всех кнопок
      cameraButtons.forEach(btn => {
        btn.classList.remove('active');
      });
      // Добавляем класс active к нажатой кнопке
      button.classList.add('active');
    });
  });
}

// Вызов функции добавления обработчиков событий после загрузки списка камер
fetchCameraList().then(() => {
  addCameraButtonClickHandlers();
});

// Clear existing file tree
function clearFileTree(parent) {
    while (parent.firstChild) {
        parent.removeChild(parent.firstChild);
    }
}

function generateFileTree(data, parent) {
    // Clear existing file tree
    clearFileTree(parent);
    // Iterate through data and generate file tree
    Object.keys(data).forEach(key => {
        const folderLi = document.createElement('li');
        const folderSpan = document.createElement('span');
        folderSpan.textContent = key;
        folderSpan.classList.add('folder');
        folderLi.appendChild(folderSpan);
        const ul = document.createElement('ul');

        ul.classList.add('collapsed'); // Add collapsed class by default

        data[key].forEach(file => {
            const fileLi = document.createElement('li');
            const fileTextNode = document.createTextNode(file);
            fileLi.appendChild(fileTextNode);
            ul.appendChild(fileLi);
        });

        folderLi.appendChild(ul);
        parent.appendChild(folderLi);

        // Add click event listener to toggle files visibility
        folderSpan.addEventListener('click', () => {
            ul.classList.toggle('collapsed');
        });
    });
}

// Fetch JSON data from
function fetch_files(camera) {
    fetch(`files.php?camera=${camera}`)
      .then(response => response.json())
      .then(data => {
        const folderTreeContainer = document.getElementById("folderTree");

        // Вызов функции добавления обработчиков событий после генерации дерева файлов
        generateFileTree(data, folderTreeContainer);
        addFileClickHandlers();



        playSecondFileDefault();

        // Автоматическое раскрытие первого элемента дерева
        const firstFolderSpan = folderTreeContainer.querySelector('.folder');
        if (firstFolderSpan) {
            const ul = firstFolderSpan.nextElementSibling;
            ul.classList.remove('collapsed');
       }

      })
      .catch(error => console.error('Error fetching data:', error));
}


function addFileClickHandlers() {
  const fileElements = document.querySelectorAll('#folderTree li ul li');
  fileElements.forEach((fileElement, index) => {
    fileElement.addEventListener('click', () => {
      const camera = document.getElementById('cam-name').innerHTML;
      const fileName = fileElement.textContent;

      // Удалите класс active у всех элементов списка файлов
      fileElements.forEach(file => {
        file.classList.remove('active');
      });
      // Добавьте класс active к проигрываемому файлу
      fileElement.classList.add('active');

      // Получите путь к видео из дерева файлов
      const folderSpan = fileElement.parentElement.parentElement.firstChild;
      const folderName = folderSpan.textContent;
      const videoPath = `${folderName}/${fileName}`;

      // Воспроизведение нового видео с сохраненной скоростью
      playNewVideo(videoPath);
    });
  });
}

const video = document.getElementById('video');

// Добавление обработчика события "ended" к элементу видео
video.addEventListener('ended', () => {
  const activeFileElement = document.querySelector('#folderTree li ul li.active');
  if (activeFileElement) {
    const previousFileElement = activeFileElement.previousElementSibling;
    if (previousFileElement) {
      // Воспроизведение предыдущего файла
      previousFileElement.click();
    } else {
      // Если предыдущего файла нет, можно воспроизвести последний файл
      const lastFileElement = document.querySelector('#folderTree li ul li:last-child');
      if (lastFileElement) {
        lastFileElement.click();
      }
    }
  }
});


// Функция для воспроизведения второго файла по умолчанию
function playSecondFileDefault() {
  const secondFileElement = document.querySelector('#folderTree li ul li:nth-child(2)');
  if (secondFileElement) {
    secondFileElement.click();
  }
}

PHP

cams.php
<?php
echo '{"cameras": ';
echo shell_exec("yaml2json /opt/go2rtc/go2rtc.yaml | jq '.streams | keys_unsorted'");
echo '}';
?>
files.php
<?php
// Function to recursively get all files in a directory
function getFiles($dir, &$visitedDirs = []) {
    $files = glob(rtrim($dir, '/') . '/*');
    $result = [];

    foreach ($files as $file) {
        if (is_dir($file) && !in_array($file, $visitedDirs)) {
            $visitedDirs[] = $file;
            $result = array_merge($result, getFiles($file, $visitedDirs));
        } else {
            $result[] = $file;
        }
    }

    return $result;
}

// Main function to get files and encode them in JSON format
function getAllFilesJSON($dir) {
    $visitedDirs = [];
    $files = getFiles($dir, $visitedDirs);
    $result = [];
    rsort($files);
    foreach ($files as $file) {
        $folder = dirname($file);
        $filename = basename($file);
//        if (!array_key_exists($folder, $result)) {
//            $result[$folder] = [];
//        }
        $result[$folder][] = $filename;
    }

    return json_encode($result);
}

//usage
$directory = 'video/'.$_GET['camera'];
$jsonData = getAllFilesJSON($directory);
echo $jsonData;
?>

python dvr.py --config_file '/path/to/config.yaml' --base_dir '/media/disk1/video' --stream_server 'rtsp://10.10.15.103:8554'

import argparse
import time
import os
import yaml
from datetime import datetime
import subprocess
import schedule
 
# Настройка парсера аргументов командной строки
parser = argparse.ArgumentParser(description='Запись видеопотоков.')
parser.add_argument('--config_file', type=str, help='Путь к файлу конфигурации', required=True)
parser.add_argument('--base_dir', type=str, help='Базовая директория для сохранения видео', required=True)
parser.add_argument('--stream_server', type=str, help='Адрес сервера потоков', required=True)
 
# Чтение аргументов командной строки
args = parser.parse_args()
 
# Функция для записи потоков
def record_streams(duration, base_dir, stream_server):
    with open(args.config_file, 'r') as file:
        config_data = yaml.safe_load(file)
    streams = config_data['streams'].keys()
 
    now = datetime.now()
    year, month, day = now.strftime("%Y"), now.strftime("%m"), now.strftime("%d")
    current_time = now.strftime("%H-%M")
    M = now.strftime("%M")
 
    processes = []
 
    for stream_name in streams:
        directory = os.path.join(base_dir, stream_name, year, month, day)
        os.makedirs(directory, exist_ok=True)
        output_file = os.path.join(directory, f"{current_time}.mp4")
        command = [
            'ffmpeg', '-hide_banner', '-loglevel', 'warning', '-threads', '2',
            '-avoid_negative_ts', 'make_zero', '-fflags', '+nobuffer+genpts+discardcorrupt',
            '-flags', 'low_delay', '-rtsp_transport', 'tcp', '-use_wallclock_as_timestamps', '1',
            '-i', f"{stream_server}/{stream_name}", '-reset_timestamps', '1', '-strftime', '1',
            '-c:v', 'copy', '-c:a', 'aac', '-strict', 'experimental', '-t', str(duration), output_file
        ]
        log_file = os.path.join(base_dir, f"{stream_name}_{M}.txt")
        # Запуск субпроцесса без ожидания его завершения
        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        processes.append(process)
 
    # Возвращаем список запущенных процессов, если нужно контролировать их в будущем
    return processes
 
# Запуск если минута не кратна 10
now = datetime.now()
if now.minute % 10 != 0:
    next_interval = (now.minute // 10 + 1) * 10
    remaining_time = (next_interval - now.minute) * 60 - now.second
    record_streams(remaining_time, args.base_dir, args.stream_server)
 
remaining_time = 600
# Планируем задачу на каждую минуту
schedule.every().hour.at(":00").do(record_streams,remaining_time, args.base_dir, args.stream_server)
schedule.every().hour.at(":10").do(record_streams,remaining_time, args.base_dir, args.stream_server)
schedule.every().hour.at(":20").do(record_streams,remaining_time, args.base_dir, args.stream_server)
schedule.every().hour.at(":30").do(record_streams,remaining_time, args.base_dir, args.stream_server)
schedule.every().hour.at(":40").do(record_streams,remaining_time, args.base_dir, args.stream_server)
schedule.every().hour.at(":50").do(record_streams,remaining_time, args.base_dir, args.stream_server)
 
# Основной цикл
while True:
    schedule.run_pending()
    time.sleep(1)
dvr.1715388415.txt.gz · Последнее изменение: 08.07.2024 11:48 (внешнее изменение)