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.

See also

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

    Note

    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