Recording#

Motion#

script to record streams captured by video devices with Motion.

We will assume that Motion is already configured and running.

You can use hardware acceleration instead of using software for the encoding process to reduce the load on the processors, although the quality is lower than software encoding. Since we are dealing with video surveillance footage we don’t care about quality so much.

Vedi anche

  • Hardware video acceleration - ArchWiki [1]

  • HWAccelIntro – FFmpeg [2]

  • Hardware/VAAPI – FFmpeg [3]

Setup#

  1. install the dependencies

    apt-get install bash ffmpeg python3-yaml python3-requests
    
  2. install fpyutils. See reference

  3. create a new user

    useradd --system -s /bin/bash -U surveillance
    passwd surveillance
    usermod -aG jobs surveillance
    
  4. create the jobs directories. See reference

    mkdir -p /home/jobs/{scripts,services}/by-user/surveillance
    
  5. create the script

    /home/jobs/scripts/by-user/surveillance/record_motion.py#
     1#!/usr/bin/env python3
     2# -*- coding: utf-8 -*-
     3#
     4# record_motion.py
     5#
     6# Copyright (C) 2019-2022 Franco Masotti (franco \D\o\T masotti {-A-T-} tutanota \D\o\T com)
     7#
     8# This program is free software: you can redistribute it and/or modify
     9# it under the terms of the GNU General Public License as published by
    10# the Free Software Foundation, either version 3 of the License, or
    11# (at your option) any later version.
    12#
    13# This program is distributed in the hope that it will be useful,
    14# but WITHOUT ANY WARRANTY; without even the implied warranty of
    15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    16# GNU General Public License for more details.
    17#
    18# You should have received a copy of the GNU General Public License
    19# along with this program.  If not, see <https://www.gnu.org/licenses/>.
    20r"""Record motion camera videos."""
    21
    22import datetime
    23import pathlib
    24import shlex
    25import sys
    26
    27import fpyutils
    28import yaml
    29
    30if __name__ == '__main__':
    31    configuration_file = shlex.quote(sys.argv[1])
    32    config = yaml.load(open(configuration_file, 'r'), Loader=yaml.SafeLoader)
    33
    34    try:
    35        message = 'started recording motion camera: ' + config[
    36            'camera_name'] + '\n'
    37        if config['notify']['gotify']['enabled']:
    38            m = config['notify']['gotify']['message'] + '\n' + message
    39            fpyutils.notify.send_gotify_message(
    40                config['notify']['gotify']['url'],
    41                config['notify']['gotify']['token'], m,
    42                config['notify']['gotify']['title'],
    43                config['notify']['gotify']['priority'])
    44        if config['notify']['email']['enabled']:
    45            fpyutils.notify.send_email(
    46                message, config['notify']['email']['smtp_server'],
    47                config['notify']['email']['port'],
    48                config['notify']['email']['sender'],
    49                config['notify']['email']['user'],
    50                config['notify']['email']['password'],
    51                config['notify']['email']['receiver'],
    52                config['notify']['email']['subject'])
    53    except Exception as e:
    54        # Ignore errors.
    55        print(e)
    56
    57    if config['ffmpeg']['quality']['global_quality'] == str():
    58        quality_string = '-q:v ' + config['ffmpeg']['quality']['quality']
    59    else:
    60        quality_string = '-global_quality ' + config['ffmpeg']['quality'][
    61            'global_quality']
    62
    63    pathlib.Path(config['dst_directory']).mkdir(parents=True, exist_ok=True)
    64    while True:
    65        # Delete videos older than 'days to keep' days.
    66        for d in pathlib.Path(config['dst_directory']).iterdir():
    67            # Work with naive datetime objects because we assume
    68            # that everyting is handled on the same computer.
    69            if (datetime.datetime.now() -
    70                    datetime.datetime.fromtimestamp(d.stat().st_mtime)
    71                ).total_seconds() > config['retention_seconds']:
    72                d.unlink()
    73
    74        # Record the video as a motion JPEG incapsulated in a Martoska file.
    75        # Usually this script is run on the same computer handling the video
    76        # stream.
    77        video_path = str(
    78            pathlib.Path(
    79                config['dst_directory'], 'video_' +
    80                str(datetime.datetime.now().strftime('%F_%H-%M-%S')) + '.mkv'))
    81        command = (config['ffmpeg']['executable'] + ' ' +
    82                   config['ffmpeg']['extra_options']['pre'] + ' -an ' +
    83                   ' -i ' + config['stream_url'] + ' ' + quality_string +
    84                   ' -video_size ' + config['ffmpeg']['video']['size'] +
    85                   ' -c:v ' + config['ffmpeg']['video']['codec'] +
    86                   ' -vframes ' +
    87                   config['ffmpeg']['video']['frames_per_file'] + ' ' +
    88                   config['ffmpeg']['extra_options']['post'] + ' ' +
    89                   video_path)
    90        fpyutils.shell.execute_command_live_output(command, dry_run=False)
    
  6. create a configuration file

    /home/jobs/scripts/by-user/surveillance/record_motion.camera1.yaml#
     1#
     2# record_motion.camera1.yaml
     3#
     4# Copyright (C) 2020,2022 Franco Masotti (franco \D\o\T masotti {-A-T-} tutanota \D\o\T com)
     5#
     6# This program is free software: you can redistribute it and/or modify
     7# it under the terms of the GNU General Public License as published by
     8# the Free Software Foundation, either version 3 of the License, or
     9# (at your option) any later version.
    10#
    11# This program is distributed in the hope that it will be useful,
    12# but WITHOUT ANY WARRANTY; without even the implied warranty of
    13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14# GNU General Public License for more details.
    15#
    16# You should have received a copy of the GNU General Public License
    17# along with this program.  If not, see <https://www.gnu.org/licenses/>.
    18
    19ffmpeg:
    20    executable: '/usr/bin/ffmpeg'
    21    video:
    22        # The total number of video frames per file before creating a new one.
    23        frames_per_file: '300'
    24        # Resolution in pixels.
    25        size: '1280x720'
    26        codec: 'mjpeg'
    27    extra_options:
    28        pre: ''
    29        post: "-b:v 15000 -r 10 -filter:v 'setpts=0.25*PTS'"
    30    quality:
    31        # If empty use quality otherwise use global_quality.
    32        global_quality: ''
    33
    34        # The nearer to 0 the higher the quality and the file size.
    35        quality: '0.1'
    36
    37stream_url: 'http://host:port'
    38
    39# dst_directory: '/home/surveillance/video/camera1'
    40dst_directory: '/tmp/camera'
    41
    42# 2 days.
    43retention_seconds: 172800
    44
    45camera_name: 'camera 1'
    46
    47notify:
    48    email:
    49        enabled: false
    50        smtp_server: 'smtp.gmail.com'
    51        port: 465
    52        sender: 'myusername@gmail.com'
    53        user: 'myusername'
    54        password: 'my awesome password'
    55        receiver: 'myusername@gmail.com'
    56        subject: 'record motion'
    57    gotify:
    58        enabled: false
    59        url: '<gotify url>'
    60        token: '<app token>'
    61        title: 'record motion'
    62        message: 'record motion started'
    63        priority: 5
    

    Nota

    If you have an intel processor that supports VAAPI (Video Acceleration API) you can use the following settings

    video:
        # [ ... ]
        codec: 'mjpeg_vaapi'
    extra_options:
        pre: '-hwaccel vaapi -hwaccel_device /dev/dri/renderD128 -hwaccel_output_format vaapi'
        # [ ... ]
    quality:
        # If empty use quality otherwise use global_quality.
        global_quality: '60'
        # [ ... ]
    # [ ... ]
    
  7. fix the permissions

    chown -R surveillance:surveillance /home/jobs/{scripts,services}/by-user/surveillance
    chmod 700 -R /home/jobs/{scripts,services}/by-user/surveillance
    
  8. run the deploy script

Footnotes