From 7a12aed45b1e1b8fe9fdb69b659984c0b73617ea Mon Sep 17 00:00:00 2001 From: Pavel Muhortov Date: Sat, 24 Jun 2023 21:54:31 +0300 Subject: [PATCH] publisher.sh absorbed cctv-scheduler.py --- .vscode/launch.json | 29 +- README.md | 63 +- archive/0.4/README.md | 169 + archive/0.4/cctv-scheduler.conf | 136 + archive/0.4/cctv-scheduler.py | 3308 +++++++++++++++++ .../0.4/publisher-template-page-1007.xml | 0 publisher.conf => archive/0.4/publisher.conf | 0 publisher.sh => archive/0.4/publisher.sh | 0 cctv-scheduler.conf | 35 +- cctv-scheduler.py | 1236 +++--- info/images/cctv-scheduler-0.5.png | Bin 0 -> 355962 bytes 11 files changed, 4406 insertions(+), 570 deletions(-) create mode 100644 archive/0.4/README.md create mode 100644 archive/0.4/cctv-scheduler.conf create mode 100644 archive/0.4/cctv-scheduler.py rename publisher-template-page-1007.xml => archive/0.4/publisher-template-page-1007.xml (100%) rename publisher.conf => archive/0.4/publisher.conf (100%) rename publisher.sh => archive/0.4/publisher.sh (100%) create mode 100755 info/images/cctv-scheduler-0.5.png diff --git a/.vscode/launch.json b/.vscode/launch.json index 3ec4bde..d9853e9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -39,7 +39,34 @@ "args": [ "-c", "-w", - "-1" + "-3" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: cctv-scheduler -p -w -3", + "type": "python", + "request": "launch", + "program": "${file}", + "args": [ + "-p", + "-w", + "-3" + ], + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "Python: cctv-scheduler -w -3 -c -p", + "type": "python", + "request": "launch", + "program": "${file}", + "args": [ + "-d", + "-1", + "-c", + "-p" ], "console": "integratedTerminal", "justMyCode": true diff --git a/README.md b/README.md index 0b9631d..e3bacc1 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,10 @@ PTZ IP-Camera management ____ - [`cctv-scheduler.py`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#cctv-scheduler-py) -- [`publisher.sh`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#publisher-sh) ____ -![cctv-scheduler](info/images/cctv-scheduler-0.4.png) +![cctv-scheduler](info/images/cctv-scheduler-0.5.png) ## `Installation` @@ -30,9 +29,6 @@ Download scripts and configs. ```bash wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/cctv-scheduler.py -O /home/user/cctv-scheduler/cctv-scheduler.py wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/cctv-scheduler.conf -O /home/user/cctv-scheduler/cctv-scheduler.conf -wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/publisher.sh -O /home/user/cctv-scheduler/publisher.sh -wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/publisher.conf -O /home/user/cctv-scheduler/publisher.conf -wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/publisher-template-page-1007.xml -O /home/user/cctv-scheduler/publisher-template-page-1007.xml ``` ### `Configuration` @@ -41,7 +37,6 @@ Edit configs. ```bash nano /home/user/cctv-scheduler/cctv-scheduler.conf -nano /home/user/cctv-scheduler/publisher.conf ``` ### `Scheduler` @@ -61,6 +56,7 @@ crontab -e > - getting temperature from DS18B20 over SSH, > - saving pictures to FTP. > - converting picture collection to video. +> - publishing video to Telegram chat and Wordpress site. > > This is only a local "proof of concept" for testing and debugging. @@ -98,6 +94,7 @@ crontab -e |**[-s, --sequences]**|run sequences from config file|`None`| |**[--config]**|custom configuration file path|`./cctv-scheduler.conf`| |**[-c, --converter]**|convert JPEG collection to MP4|`None`| +|**[-p, --publisher]**|publish content from templates|`None`| |**[-d, --day]**|day in amount of days from today, for which the publication or conversion is made|`0`| |**[-w, --week]**|week in amount of weeks from today, for which the publication or conversion is made|`0`| |**[-m, --month]**|month in amount of months from today, for which the publication or conversion is made|`0`| @@ -116,54 +113,8 @@ Example usage with cron: # crontab -e * * * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -b 0 * * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -s -1 0 * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -d -1 -7 0 * * 1 /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -w -1 -30 0 1 * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -m -1 -36 0 1 1 * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -y -1 -``` - -____ - -## `publisher`.sh - -**Description:** -> Uploading MP4 to [Wordpress](https://wordpress.com/) and [Telegram](https://web.telegram.org/). -> Additionally: -> -> - editing [Wordpress](https://codex.wordpress.org/XML-RPC_WordPress_API) page from template -> - recompressing video if size [over 50MB](https://core.telegram.org/bots/api#sendvideo) -> -> This is only a local "proof of conept" for testing and debugging. - -**Dependencies:** -> -> - [bash](https://www.gnu.org/software/bash/) (tested version 5.1.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> - [curl](https://curl.se/download.html) (tested version 7.74 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> - [ffmpeg](https://ffmpeg.org/download.html) (tested version 4.3.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> - [libxml2-utils](https://gitlab.gnome.org/GNOME/libxml2) (tested version 2.9.10 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> - [jq](https://stedolan.github.io/jq/download/) (tested version 1.6 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) -> - -| POSITION | PARAMETERS | DESCRIPTION | DEFAULT | -|-----------|--------------|------------------------|---------------| -| 1 | **[qn]** |execution without pauses|| -| 2 | **[/path/to/conf]** |path to config| `./publisher.conf` | -| 3 | **[-d\|-w\|-m\|-y]** |periods: '' - 0 day \| '-d' - -X day \| '-w' - -X week \| '-m' - -X month \| '-y' - -X year|`''`| -| 4 | **[1\|2\|3..XXX]** |multiplier for period: '' - 1 day\|week\|month\|year|`1`| -| 5 | **[--onlytg\|--onlywp]** |'--onlytg' - only publish to Telegram \|'--onlywp' - only publish to Wordpress|| - -Example usage in terminal with bash for publish to Telegram today's MP4: - -```bash -bash ./publisher.sh - ./publisher.conf - - - - --onlytg -``` - -Example usage with cron: - -```bash -# crontab -e -1 1 * * * bash /home/user/cctv-scheduler/publisher.sh qn - -d -7 1 * * 1 bash /home/user/cctv-scheduler/publisher.sh qn - -w -30 1 1 * * bash /home/user/cctv-scheduler/publisher.sh qn - -m -36 1 1 1 * bash /home/user/cctv-scheduler/publisher.sh qn - -y +1 0 * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -d -1 -c -p +7 0 * * 1 /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -w -1 -c -p +30 0 1 * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -m -1 -c -p +36 0 1 1 * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -y -1 -c -p ``` diff --git a/archive/0.4/README.md b/archive/0.4/README.md new file mode 100644 index 0000000..0b9631d --- /dev/null +++ b/archive/0.4/README.md @@ -0,0 +1,169 @@ +# cctv-scheduler + +PTZ IP-Camera management +____ + +- [`cctv-scheduler.py`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#cctv-scheduler-py) +- [`publisher.sh`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#publisher-sh) + +____ + +![cctv-scheduler](info/images/cctv-scheduler-0.4.png) + +## `Installation` + +### `Requirements` + +Cameras settings: +> +> - Configuration -> System -> Security -> Authentication -> RTSP Authentication: digest/basic +> - Configuration -> System -> Security -> Authentication -> WEB Authentication: digest/basic +> - Configuration -> Network -> Advanced Settings -> Integration Protocol -> Enable Hikvision-CGI: Enabled +> - Configuration -> Network -> Advanced Settings -> Integration Protocol -> Hikvision-CGI Authentication: digest/basic + +Look at the description of dependencies and install the necessary. + +### `Downloading` + +Download scripts and configs. + +```bash +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/cctv-scheduler.py -O /home/user/cctv-scheduler/cctv-scheduler.py +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/cctv-scheduler.conf -O /home/user/cctv-scheduler/cctv-scheduler.conf +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/publisher.sh -O /home/user/cctv-scheduler/publisher.sh +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/publisher.conf -O /home/user/cctv-scheduler/publisher.conf +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/publisher-template-page-1007.xml -O /home/user/cctv-scheduler/publisher-template-page-1007.xml +``` + +### `Configuration` + +Edit configs. + +```bash +nano /home/user/cctv-scheduler/cctv-scheduler.conf +nano /home/user/cctv-scheduler/publisher.conf +``` + +### `Scheduler` + +Look at examples and edit scheduler tasks: + +```bash +crontab -e +``` + +## `cctv-scheduler`.py + +**Description:** +> [Hikvision](https://git.hmp.today/pavel.muhortov/cctv-scheduler/src/branch/master/info/hikvision/manual/isapi.pdf) PTZ IP-Camera management. Media streaming. Images to video converting. +> Additionally: +> +> - getting temperature from DS18B20 over SSH, +> - saving pictures to FTP. +> - converting picture collection to video. +> +> This is only a local "proof of concept" for testing and debugging. + +**Dependencies:** +> +> - [Python 3](https://www.python.org/downloads/) (tested version 3.9.5 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - [paramiko](https://www.paramiko.org/) Python 3 module (tested version 3.1.0) +> - [ffmpeg](https://ffmpeg.org) (tested version 4.3.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - specified record pictures filesystem organization +> +>```bash +> # record pictures filesystem organization example +>/root/ +> /2022/ +> /12/ +> /52/ +> /31/ +> /image-01_2022.12.31_time.jpeg +> /image-02_2022.12.31_time.jpeg +> /2023/ +> /01/ +> /01/ +> /02/ +> /image-01_2023.01.02_time.jpeg +> /image-02_2023.01.02_time.jpeg +> /03/ +> /image-01_2023.01.03_time.jpeg +> /image-02_2023.01.03_time.jpeg +>``` + +| PARAMETERS | DESCRIPTION | DEFAULT| +|-------------|-------------|--------| +|**[-h]**|print help and exit|| +|**[-b, --broadcast]**|streaming media to destination|`None`| +|**[-s, --sequences]**|run sequences from config file|`None`| +|**[--config]**|custom configuration file path|`./cctv-scheduler.conf`| +|**[-c, --converter]**|convert JPEG collection to MP4|`None`| +|**[-d, --day]**|day in amount of days from today, for which the publication or conversion is made|`0`| +|**[-w, --week]**|week in amount of weeks from today, for which the publication or conversion is made|`0`| +|**[-m, --month]**|month in amount of months from today, for which the publication or conversion is made|`0`| +|**[-y, --year]**|year in amount of years from today, for which the publication or conversion is made|`0`| + +Example usage in terminal with make the script executable: + +```bash +chmod u+x ./cctv-scheduler.py +./cctv-scheduler.py -s --config /home/user/cctv-scheduler/cctv-scheduler.conf +``` + +Example usage with cron: + +```bash +# crontab -e +* * * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -b +0 * * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -s +1 0 * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -d -1 +7 0 * * 1 /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -w -1 +30 0 1 * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -m -1 +36 0 1 1 * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -c -y -1 +``` + +____ + +## `publisher`.sh + +**Description:** +> Uploading MP4 to [Wordpress](https://wordpress.com/) and [Telegram](https://web.telegram.org/). +> Additionally: +> +> - editing [Wordpress](https://codex.wordpress.org/XML-RPC_WordPress_API) page from template +> - recompressing video if size [over 50MB](https://core.telegram.org/bots/api#sendvideo) +> +> This is only a local "proof of conept" for testing and debugging. + +**Dependencies:** +> +> - [bash](https://www.gnu.org/software/bash/) (tested version 5.1.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - [curl](https://curl.se/download.html) (tested version 7.74 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - [ffmpeg](https://ffmpeg.org/download.html) (tested version 4.3.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - [libxml2-utils](https://gitlab.gnome.org/GNOME/libxml2) (tested version 2.9.10 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - [jq](https://stedolan.github.io/jq/download/) (tested version 1.6 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> + +| POSITION | PARAMETERS | DESCRIPTION | DEFAULT | +|-----------|--------------|------------------------|---------------| +| 1 | **[qn]** |execution without pauses|| +| 2 | **[/path/to/conf]** |path to config| `./publisher.conf` | +| 3 | **[-d\|-w\|-m\|-y]** |periods: '' - 0 day \| '-d' - -X day \| '-w' - -X week \| '-m' - -X month \| '-y' - -X year|`''`| +| 4 | **[1\|2\|3..XXX]** |multiplier for period: '' - 1 day\|week\|month\|year|`1`| +| 5 | **[--onlytg\|--onlywp]** |'--onlytg' - only publish to Telegram \|'--onlywp' - only publish to Wordpress|| + +Example usage in terminal with bash for publish to Telegram today's MP4: + +```bash +bash ./publisher.sh - ./publisher.conf - - - - --onlytg +``` + +Example usage with cron: + +```bash +# crontab -e +1 1 * * * bash /home/user/cctv-scheduler/publisher.sh qn - -d +7 1 * * 1 bash /home/user/cctv-scheduler/publisher.sh qn - -w +30 1 1 * * bash /home/user/cctv-scheduler/publisher.sh qn - -m +36 1 1 1 * bash /home/user/cctv-scheduler/publisher.sh qn - -y +``` diff --git a/archive/0.4/cctv-scheduler.conf b/archive/0.4/cctv-scheduler.conf new file mode 100644 index 0000000..743d0d3 --- /dev/null +++ b/archive/0.4/cctv-scheduler.conf @@ -0,0 +1,136 @@ +[common] +# By default, a temporary files directory is created in the same path where the script is located. +# If you need change it, uncomment the parameter and set the path you want. +#temp_path = /tmp/cctv-scheduler +# +# By default, logs use the same directory where the script is located. +# If you need change it, uncomment the parameter and set the path you want. +#log_root = /var/log/cctv-scheduler +# +# The default log level is "INFO". +# If you get errors or want to change the logging level, uncomment the parameter and set the level you want: +# DEBUG, INFO, WARNING, ERROR, CRITICAL. +#log_level = DEBUG + + +[enable-broadcast] +# List the broadcast block names. Only blocks with the TRUE value will be used. +camera.test.local = true + + +[enable-sequences] +# List the sequence/camera block names. Only blocks with the TRUE value will be used. +camera.test.local = true + + +[enable-sensors] +# List the sensor block names. Only blocks with the TRUE value will be used. +sensor.test.local = true + + +[enable-convert] +# List the convert block names. Only blocks with the TRUE value will be used. +camera.test.local = true + + +[broadcast-config:camera.test.local] +# Broadcast parameter description block always starts with "broadcast-config:". +src = rtsp://user:pass@192.168.254.253:554/Streaming/Channels/101,http://radio.fm:8000/stream.mp3 +dst = rtp://239.0.0.1:5554 +# Optionality you can change video stream framerate. +#fps = 25 +# +# Optionality you can set YouTube recommended preset: +# 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p. +#preset = 1080p +# +# By default ffmpeg expected in /usr/bin/ffmpeg or C:\Program Files\ffmpeg\bin\ffmpeg.exe. +# If you need change it, uncomment the parameter and set the path you want. +#ffpath = /usr/bin/ffmpeg +# +# If you get program freezes because your network unstable, uncomment the parameter. +#watchdog = true +# +# By default, watchdog waits 5 seconds before terminating the program. +# If you need change it, uncomment the parameter and set the path you want. +#watchsec = 15 +# +# If you use crontab or other scheduler to run, uncomment the parameter to prevent process overlap. +#onlyonce = true + + +[sensor-config:sensor.test.local] +# Remote host's sensor parameter description block always starts with "sensor-config:". +hostname = 192.168.254.252 +username = user +userpass = pass +# To recognize options for polling a sensor, you must specify the type of sensor. +# Supported types: +# ds18b20 +nodetype = ds18b20 +nodename = 28-1a2b3c4d5e6f + + +[camera-config:camera.test.local] +# Camera parameter description block always starts with "camera-config:". +hostname = 192.168.254.253 +username = user +userpass = pass +# If a record directory on a remote host is used, a username and password must be specified. +# Supported protocols: +# FTP, SFTP. +records_root_path = ftp://192.168.254.254/Records/camera.test.local +records_root_user = user +records_root_pass = pass + + +[camera-sequences:camera.test.local] +# Camera sequence description block always starts with "camera-sequences:". +# Place only the sequence of PTZ-actions in this block! +# Variable name can be anything. Only 'downloadjpeg' is using this for filename prefix. +# Available actions: +# setcamerapos, setcameramov, settextonosd, downloadjpeg, capabilities, getcamerapos, +# setptzmovyyu, setptzmovyyd, setptzmovxxl, setptzmovxxr, setptzmovzzi, setptzmovzzo, +# setptzpreset, setptztostop, setmovtohome, setposashome, rebootcamera. +# +# Format (spaces are used for readability only): +# name = action, x, y, zoom, preset, speed, d(ms), w(s), text, notes or anything +step001 = capabilities, -, -, -, -, -, -, 3, , +step002 = getcamerapos, -, -, -, -, -, -, 3, , +step011 = setmovtohome, -, -, -, -, -, -, 15, , +step021 = setptzmovyyd, -, -, -, -, 2, -, 5, , 'speed: 1..7' +step022 = setptzmovyyu, -, -, -, -, 4, -, 3, , 'speed: 1..7' +step023 = setptzmovxxl, -, -, -, -, 4, -, 3, , 'speed: 1..7' +step024 = setptzmovxxr, -, -, -, -, 2, -, 5, , 'speed: 1..7' +step025 = setptzmovzzi, -, -, -, -, 7, -, 3, , 'speed: 1..7' +step026 = setptzmovzzo, -, -, -, -, 7, -, 3, , 'speed: 1..7' +step027 = setptztostop, -, -, -, -, -, -, 3, , +step031 = setptzpreset, -, -, -, 2, 1, -, 15, , 'speed: 1..7' +step041 = setcameramov, 33, 33, 66, -, -, -, 5, , 'x: -100..100, y: -100..100, z: -100..100, duration: 0..180000' +step042 = setcameramov, -66, -66, -99, -, -, 5000, 0, , 'x: -100..100, y: -100..100, z: -100..100, duration: 0..180000' +step043 = setcamerapos, 0, 0, 0, -, -, -, 15, , 'x: 0..3600, y: -900..2700, z: 0..1000' +step051 = setposashome, -, -, -, -, -, -, 3.5, , +step061 = settextonosd, 0, 0, -, -, -, -, 3, hello, 'x|y: osd text position, text: text for osd without quotes or commas' +step062 = settextonosd, 0, 0, -, -, -, -, 3, sensor-config:sensor.test.local, 'if a sensor configuration is specified, the sensor value is used instead of text' +step063 = settextonosd, 0, 0, -, -, -, -, 3, , 'an empty text value is used to clear the osd' +step071 = downloadjpeg, 1920, 1080, -, -, -, -, 5, , 'name: filename prefix, x|y: camera width|height resolution' +step999 = rebootcamera, -, -, -, -, -, -, 120, , + + +[convert-config:camera.test.local] +# Converter parameter description block always starts with "convert-config:". +image_find_names = step071, image-01, image-02 +# If a image root or destination video directories on a remote host is used, a username and password must be specified. +# Supported protocols: +# FTP. +image_root_path = ftp://192.168.254.254/Records/camera.test.local +image_root_user = user +image_root_pass = pass + +video_dest_path = ftp://192.168.254.254/Downloads +video_dest_user = user +video_dest_pass = pass + +video_scale_x = 1920 +video_scale_y = 1080 +video_framerate = 25 \ No newline at end of file diff --git a/archive/0.4/cctv-scheduler.py b/archive/0.4/cctv-scheduler.py new file mode 100644 index 0000000..cd5df83 --- /dev/null +++ b/archive/0.4/cctv-scheduler.py @@ -0,0 +1,3308 @@ +#!/usr/bin/env python3 +# pylint: disable=C0103,C0302,C0114,W0621 + + +import calendar +import base64 +import datetime +import json +import logging +from random import choice +import re +import urllib.request +from argparse import ArgumentParser +from ftplib import FTP +from multiprocessing import Process, Queue +from os import environ, makedirs, path, remove, replace, rmdir, sep, stat, walk +from string import ascii_letters, digits +from subprocess import Popen, PIPE, STDOUT +from sys import platform +from time import sleep +import requests +from paramiko import SSHClient, AutoAddPolicy + + +class Parse: + """Parser of configs, arguments, parameters. + """ + # pylint: disable=C0123 + def __init__(self, parameters, block: str = None) -> None: + """Object constructor. + + Args: + parameters: dictionary as "key":"value" or + ArgumentParser class object or + string path to the file or + string as "var1=val1;var2=val2". + block (str, optional): name of target block from text. Defaults to None. + """ + self.path = '' + self.data = {} + if type(parameters) is dict: + self._dict2dict(parameters) + if type(parameters) is ArgumentParser: + self._dict2dict(self.argv2dict(parameters)) + if type(parameters) is str: + if path.exists(parameters): + self._dict2dict( + self.strs2dict( + self.conf2strs(parameters), + block + ) + ) + self.path = parameters + else: + self._dict2dict(self.strs2dict(parameters, block)) + + def __str__(self) -> str: + """Overrides method for print(object). + + Returns: + str: string with contents of the object's dictionary. + """ + string = '' + for key, val in self.data.items(): + string += str(type(val)) + ' ' + str(key) + ' = ' + str(val) + '\n' + return string + + def _dict2dict(self, dictionary: dict) -> None: + """Updates or adds dictionary data. + + Args: + dictionary (dict): dictionary as "key":"value". + """ + self.data.update(dictionary) + + # pylint: disable=C0206 + def expand(self, store: str = None) -> dict: + """Expand dictionary "key":"name.conf" to dictionary "key":{subkey: subval}. + + Args: + store (str, optional): path to directory with name.conf. Defaults to None. + + Returns: + dict: expanded dictionary as "key":{subkey: subval}. + """ + for key in self.data: + if store: + config = store + sep + self.data[key] + else: + config = self.data[key] + with open(config, encoding='UTF-8') as file: + self.data[key] = Parse(file.read()).data + return self.data + + @classmethod + def argv2dict(cls, parser: ArgumentParser) -> dict: + """Converts startup arguments to a dictionary. + + Args: + parser (ArgumentParser): argparse.ArgumentParser class object. + + Returns: + dict: dictionary as "key":"value". + """ + parser = ArgumentParser(add_help=False, parents=[parser]) + return vars(parser.parse_args()) + + @classmethod + def conf2strs(cls, config: str) -> str: + """Builds a dictionary from a file containing parameters. + + Args: + config (str): path to the config file. + + Returns: + str: string as "var1=val1;\nvar2=val2;". + """ + with open(config, encoding='UTF-8') as file: + raw = file.read() + strs = '' + for line in raw.splitlines(): + if not line.lstrip().startswith('#'): + strs += line + '\n' + return strs + + @classmethod + def strs2dict(cls, strings: str, blockname: str) -> dict: + """Builds a dictionary from a strings containing parameters. + + Args: + strings (str): string as "var1=val1;var2=val2;". + blockname (str): name of target block from text. + + Returns: + dict: dictionary as "key":"value". + """ + dictionary = {} + if blockname: + strings = cls.block(blockname, strings) + for line in strings.replace('\n', ';').split(';'): + if not line.lstrip().startswith('#') and "=" in line: + dictionary[line.split('=')[0].strip()] = ( + line.split('=')[1].strip().split(';')[0].strip() + ) + return dictionary + + @classmethod + def str2bool(cls, value: str) -> bool: + """Converts a string value to boolean. + + Args: + value (str): string containing "true" or "false", "yes" or "no", "1" or "0". + + Returns: + bool: bool True or False. + """ + return str(value).lower() in ("true", "yes", "1") + + @classmethod + def block(cls, blockname: str, text: str) -> str: + """Cuts a block of text between line [blockname] and line [next block] or EOF. + + Args: + blockname (str): string in [] after which the block starts. + text (str): string of text from which the block is needed. + + Returns: + str: string of text between line [block name] and line [next block]. + """ + level = 1 + save = False + result = '' + for line in text.splitlines(): + if line.startswith('[') and blockname in line: + level = line.count('[') + save = True + elif line.startswith('[') and '['*level in line: + save = False + elif save: + result += line + '\n' + return result + + +class Connect: + # pylint: disable=W0105 + """Set of connection methods (functions) for various protocols. + """ + @staticmethod + # pylint: disable=W0102, W0718 + def http( + url: str, + method: str = 'GET', + username: str = '', + password: str = '', + authtype: (str, type(None)) = None, + contenttype: str = 'text/plain', + contentdata: (str, bytes) = '', + headers: dict = {} + ) -> dict: + """Handling HTTP request. + + Args: + url (str): Handling HTTP request. + method (str, optional): HTTP request method. Defaults to 'GET'. + username (str, optional): username for url authentication. Defaults to ''. + password (str, optional): password for url authentication. Defaults to ''. + authtype (str, None, optional): digest|basic authentication type. Defaults to None. + contenttype (str, optional): 'Content-Type' header. Defaults to 'text/plain'. + contentdata (str, bytes, optional): content data. Defaults to ''. + headers (dict, optional): additional headers. Defaults to {}. + + Returns: + dict: {'success':bool,'result':HTTP response or 'ERROR'}. + """ + if Do.args_valid(locals(), Connect.http.__annotations__): + if contentdata != '': + headers['Content-Type'] = contenttype + if isinstance(contentdata, str): + contentdata = bytes(contentdata.encode('utf-8')) + + # Preparing authorization + if authtype: + pswd = urllib.request.HTTPPasswordMgrWithDefaultRealm() + pswd.add_password(None, url, username, password) + if authtype == 'basic': + auth = urllib.request.HTTPBasicAuthHandler(pswd) + token = base64.b64encode((username + ':' + password).encode()) + headers['Authorization'] = 'Basic ' + token.decode('utf-8') + if authtype == 'digest': + auth = urllib.request.HTTPDigestAuthHandler(pswd) + urllib.request.install_opener(urllib.request.build_opener(auth)) + + # Preparing request + request = urllib.request.Request( + url=url, + data=contentdata, + method=method + ) + for key, val in headers.items(): + request.add_header(key, val) + if len(contentdata) > 128: + contentdata = contentdata[:64] + b' ... ' + contentdata[-64:] + logging.debug(msg='' + + '\n' + 'uri: ' + url + + '\n' + 'method: ' + method + + '\n' + 'username: ' + username + + '\n' + 'password: ' + password + + '\n' + 'authtype: ' + str(authtype) + + '\n' + 'headers: ' + json.dumps(headers, indent=2) + + '\n' + 'content-data: ' + str(contentdata) + ) + + # Response + try: + response = urllib.request.urlopen(request).read() + if not response.startswith(b'\xff\xd8'): + response = str(response.decode('utf-8')) + return {"success": True, "result": response} + except Exception as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + return {"success": False, "result": "ERROR"} + + @staticmethod + # pylint: disable=W0718 + def ssh_commands( + command: str, + hostname: str, + username: str, + password: str, + port: int = 22 + ) -> str: + """Handling SSH command executing. + + Args: + command (str): command for executing. + hostname (str): remote hostname or ip address. + username (str): remote host username. + password (str): remote host password. + port (int, optional): remote host connection port. Defaults to 22. + + Returns: + str: terminal response or 'ERROR'. + """ + client = SSHClient() + client.set_missing_host_key_policy(AutoAddPolicy()) + try: + client.connect(hostname=hostname, username=username, password=password, port=port) + stdin, stdout, stderr = client.exec_command(command=command, get_pty=True) + if 'sudo' in command: + stdin.write(password + '\n') + stdin.flush() + stdout.flush() + data = stdout.read() + stderr.read() + client.close() + return data.decode('utf-8') + except Exception as error: + logging.debug( + msg='' + + '\n' + 'host: ' + hostname + ':' + str(port) + + '\n' + 'user: ' + username + + '\n' + 'pass: ' + password + + '\n' + 'command: ' + command + + '\n' + 'error: ' + str(error) + ) + return 'ERROR' + + @staticmethod + # pylint: disable=W0718 + def ssh_put_file( + src_file: str, + dst_file: str, + hostname: str, + username: str, + password: str, + port: int = 22 + ) -> str: + """Handling SFTP upload file. + + Args: + src_file (str): /local/path/to/file. + dst_file (str): /remote/path/to/file. + hostname (str): remote hostname or ip address. + username (str): remote host username. + password (str): remote host password. + port (int, optional): remote host connection port. Defaults to 22. + + Returns: + str: '/remote/path/to/file' or 'ERROR'. + """ + client = SSHClient() + client.set_missing_host_key_policy(AutoAddPolicy()) + try: + client.connect(hostname=hostname, username=username, password=password, port=port) + client.exec_command('mkdir -p ' + path.dirname(dst_file)) + try: + sftp = client.open_sftp() + sftp.put(localpath=src_file, remotepath=dst_file) + sftp.stat(dst_file) + sftp.close() + return dst_file + except Exception as error: + logging.debug( + msg='' + + '\n' + 'dst_file: ' + dst_file + + '\n' + 'error: ' + str(error) + ) + return 'ERROR' + except Exception as error: + logging.debug( + msg='' + + '\n' + 'host: ' + hostname + ':' + str(port) + + '\n' + 'user: ' + username + + '\n' + 'pass: ' + password + + '\n' + 'src_file: ' + src_file + + '\n' + 'dst_file: ' + dst_file + + '\n' + 'error: ' + str(error) + ) + return 'ERROR' + ''' + @staticmethod + def ssh_get_file(src_file: str, dst_file: str, hostname: str, username: str, password: str, port: int = 22) -> str: + """Handling SFTP download file. + + Args: + src_file (str): /remote/path/to/file. + dst_file (str): /local/path/to/file. + hostname (str): remote hostname or ip address. + username (str): remote host username. + password (str): remote host password. + port (int, optional): remote host connection port. Defaults to 22. + + Returns: + str: '/local/path/to/file' or 'ERROR'. + """ + client = SSHClient() + client.set_missing_host_key_policy(AutoAddPolicy()) + try: + client.connect(hostname=hostname, username=username, password=password, port=port) + with client.open_sftp() as sftp: + sftp.get(remotepath=src_file, localpath=dst_file) + client.close() + except Exception as error: + logging.debug( + msg='' + + '\n' + 'host: ' + hostname + ':' + str(port) + + '\n' + 'user: ' + username + + '\n' + 'pass: ' + password + + '\n' + 'src_file: ' + src_file + + '\n' + 'dst_file: ' + dst_file + + '\n' + 'error: ' + str(error) + ) + return 'ERROR' + ''' + + @staticmethod + # pylint: disable=W0718 + def ftp_file_search( + root_path: (str), + search_name: (str, type(None)) = None, + ftp: FTP = None, + hostname: str = None, + username: str = None, + password: str = None + ) -> list: + """Search files over FTP. + + Args: + root_path (str): where to search. + search_name (str, None, optional): full or partial filename for the filter. + Defaults to None. + ftp (FTP, optional): FTP object generated by recursive search. Defaults to None. + hostname (str, optional): ftp hostname. Defaults to None. + username (str, optional): ftp username. Defaults to None. + password (str, optional): ftp password. Defaults to None. + + Returns: + list: list of found files. + """ + parent = False + if not ftp: + try: + ftp = FTP(host=hostname) + ftp.login(user=username, passwd=password) + parent = True + except Exception: + pass + + result = [] + ftp.cwd(root_path) + for file in ftp.mlsd(): + if file[1]['type'] == 'dir': + result = result + Connect.ftp_file_search( + root_path=root_path + "/" + file[0], + search_name=search_name, + ftp=ftp + ) + elif file[1]['type'] == 'file': + if search_name: + if search_name in file[0]: + result.append(root_path + "/" + file[0]) + else: + result.append(root_path + "/" + file[0]) + + if parent: + ftp.close() + result.sort() + return result + + @staticmethod + # pylint: disable=W0718,C0116 + def ftp_get_file(src_file: str, dst_file: str, hostname: str, username: str, password: str): + ftp = FTP(host=hostname) + try: + ftp.login(user=username, passwd=password) + with open(dst_file, "wb+") as file: + ftp.retrbinary(f"RETR {src_file}", file.write) + ftp.quit() + return True + except Exception as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + return False + + @staticmethod + # pylint: disable=W0718,C0116 + def ftp_put_file( + src_file: str, + dst_file: str, + hostname: str, + username: str, + password: str + ) -> bool: + dst_path = dst_file.split('/')[:-1] + ftp = FTP(host=hostname) + try: + ftp.login(user=username, passwd=password) + for path_item in dst_path: + if path_item.strip() == '': + continue + path_item = path_item.replace('/', '') + try: + ftp.cwd(path_item) + except Exception: + ftp.mkd(path_item) + ftp.cwd(path_item) + with open(src_file, "rb") as file: + ftp.storbinary(f"STOR {dst_file}", file) + ftp.quit() + return True + except Exception as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + return False + + +class HikISAPI(Connect): + """Representing Hikvision device with ISAPI. + The class inherits the necessary connection methods of the Connect class + """ + def __init__( + self, + hostname: str, + username: str, userpass: str, + authtype: str = 'digest', + hostport: int = 80, protocol: str = 'http', + channel: int = 101, videoid: int = 1 + ) -> None: + """Object constructor. + + Args: + hostname (str): camera hostname or ip address. + username (str): camera admin username. + userpass (str): camera admin password. + authtype (str, optional): digest|basic camera authentication type. Defaults to 'digest'. + hostport (int, optional): camera connection port. Defaults to 80. + protocol (str, optional): camera connection protocol. Defaults to 'http'. + channel (int, optional): camera channel id. Defaults to 101. + videoid (int, optional): camera video id. Defaults to 1. + """ + self._host = hostname + self._port = hostport + self._user = username + self._pswd = userpass + self._auth = authtype + self._prot = protocol + self._chan = channel + self._viid = videoid + + def __call( + self, + url: str, method: str = 'GET', + contenttype: str = 'application/x-www-form-urlencoded', + contentdata: str = '' + ) -> str: + """Send request to camera. + + Args: + url (str): API path for request. + method (str, optional): HTTP request method. Defaults to 'GET'. + contenttype (str, optional): Content-Type header. + Defaults to 'application/x-www-form-urlencoded'. + contentdata (str, optional): data for send with request. Defaults to ''. + + Returns: + str: HTTP response content or 'ERROR'. + """ + response = self.http( + url=url, method=method, + username=self._user, password=self._pswd, authtype=self._auth, + contenttype=contenttype, contentdata=contentdata + ) + if response['success']: + return response['result'] + else: + return 'ERROR' + + def capabilities(self) -> bool: + """Get camera capabilities. + + Returns: + bool: True if successed. Printing a response with a logger at the INFO level. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._viid) + "/capabilities" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.info(msg='\n' + response + '\n') + return True + else: + return False + + def downloadjpeg( + self, + dst_file: str = path.splitext(__file__)[0] + '.jpeg', + x: int = 1920, + y: int = 1080 + ) -> bool: + """Get static picture from camera. + + Args: + dst_file (str, optional): abs picture's path to save. Defaults to scriptname+'.jpeg'. + x (int, optional): picture width. Defaults to 1920. + y (int, optional): picture height. Defaults to 1080. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/Streaming/channels/" + str(self._viid) + + "/picture?snapShotImageType=JPEG&videoResolutionWidth=" + + str(x) + "&videoResolutionHeight=" + str(y) + ) + with open(dst_file, "wb") as file: + response = self.__call(url=url, method='GET') + if response != 'ERROR': + file.write(response) + logging.debug(msg='\n' + dst_file + '\n') + return True + else: + return False + + def getcamerapos(self) -> bool: + """Get current camera position. + + Returns: + bool: True if successed. Printing a response with a logger at the INFO level. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._chan) + "/status" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.info(msg='\n' + response + '\n') + return True + else: + return False + + def rebootcamera(self) -> bool: + """Set camera reboot command. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/System/reboot" + ) + response = self.__call(url=url, method="PUT") + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzmovyyu(self, speed: int = 1) -> bool: + """Start camera moving to up. + + Args: + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=TILT_UP&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzmovyyd(self, speed: int = 1) -> bool: + """Start camera moving to down. + + Args: + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=TILT_DOWN&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzmovxxl(self, speed: int = 1) -> bool: + """Start camera moving to left. + + Args: + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=PAN_LEFT&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzmovxxr(self, speed: int = 1) -> bool: + """Start camera moving to right. + + Args: + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=PAN_RIGHT&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzmovzzi(self, speed: int = 1) -> bool: + """Start camera zoom in. + + Args: + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=ZOOM_OUT&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzmovzzo(self, speed: int = 1) -> bool: + """Start camera zoom out. + + Args: + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=ZOOM_IN&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptzpreset(self, preset: int, speed: int = 1) -> bool: + """Start camera moving to preset. + + Args: + preset (int): saved preset number. + speed (int, optional): moving speed from 1 to 7. Defaults to 1. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=GOTO_PRESET&presetNo=" + str(preset) + + "&speed=" + str(speed) + + "&mode=start" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setptztostop(self) -> bool: + """Stop any camera moving. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/PTZ/channels/" + str(self._viid) + + "/PTZControl?command=GOTO_PRESET&mode=stop" + ) + response = self.__call(url=url, method='GET') + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setcamerapos(self, x: int = 0, y: int = 0, z: int = 0) -> bool: + """Set camera moving to absolute position. + + Args: + x (int, optional): horisontal camera position from 0 to 3600. Defaults to 0. + y (int, optional): vertical camera position from -900 to 2700. Defaults to 0. + z (int, optional): zoom camera position from 0 to 1000. Defaults to 0. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._chan) + + "/absolute" + ) + xml = ''.join( + '' + + '' + + '' + str(y) + '' + + '' + str(x) + '' + + '' + str(z) + '' + + '' + ) + response = self.__call(url=url, method="PUT", contenttype="text/xml", contentdata=xml) + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def __setcameramovcon(self, x: int = 0, y: int = 0, z: int = 0) -> bool: + """Set camera moving to direction until other signal or 180 seconds elapse. + + Args: + x (int, optional): acceleration of horizontal camera movement from -100 to 100. + Defaults to 0. + y (int, optional): acceleration of vertical camera movement from -100 to 100. + Defaults to 0. + z (int, optional): acceleration of zoom camera movement from -100 to 100. + Defaults to 0. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._chan) + + "/continuous" + ) + xml = ''.join( + '' + + '' + + '' + str(x) + '' + + '' + str(y) + '' + + '' + str(z) + '' + + '' + ) + response = self.__call(url=url, method="PUT", contenttype="text/xml", contentdata=xml) + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def __setcameramovmom(self, x: int = 0, y: int = 0, z: int = 0, t: int = 180000) -> bool: + """Set camera moving to direction until other signal or duration elapse. + + Args: + x (int, optional): acceleration of horizontal camera movement from -100 to 100. + Defaults to 0. + y (int, optional): acceleration of vertical camera movement from -100 to 100. + Defaults to 0. + z (int, optional): acceleration of zoom camera movement from -100 to 100. + Defaults to 0. + t (int, optional): duration in ms of acceleration from 0 to 180000. + Defaults to 180000. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._chan) + + "/momentary" + ) + xml = ''.join( + '' + + '' + + '' + str(x) + '' + + '' + str(y) + '' + + '' + str(z) + '' + + '' + str(t) + '' + + '' + ) + response = self.__call(url=url, method="PUT", contenttype="text/xml", contentdata=xml) + if response != 'ERROR': + sleep(t/1000) + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setcameramov(self, x: int = 0, y: int = 0, z: int = 0, t: int = 0) -> bool: + """Set camera moving to direction (polymorph abstraction). + + Args: + x (int, optional): acceleration of horizontal camera movement from -100 to 100. + Defaults to 0. + y (int, optional): acceleration of vertical camera movement from -100 to 100. + Defaults to 0. + z (int, optional): acceleration of zoom camera movement from -100 to 100. + Defaults to 0. + t (int, optional): duration in ms of acceleration from 0 to 180000. + Defaults to 0. + + Returns: + bool: True if successed. + """ + if t == '-' or int(t) == 0: + return self.__setcameramovcon(x=int(x), y=int(y), z=int(z)) + else: + return self.__setcameramovmom(x=int(x), y=int(y), z=int(z), t=int(t)) + + def setmovtohome(self) -> bool: + """Set camera moving to homeposition. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._chan) + + "/homeposition/goto" + ) + xml = ''.join( + '' + + '' + ) + response = self.__call(url=url, method="PUT", contenttype="text/xml", contentdata=xml) + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def setposashome(self) -> bool: + """Save current camera position as homeposition. + + Returns: + bool: True if successed. + """ + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/PTZCtrl/channels/" + str(self._chan) + + "/homeposition" + ) + xml = ''.join( + '' + + '' + ) + response = self.__call(url=url, method="PUT", contenttype="text/xml", contentdata=xml) + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + def settextonosd( + self, + enabled: str = "true", + x: int = 0, + y: int = 0, + message: str = "" + ) -> bool: + """Set message as video overlay text. + + Args: + enabled (str, optional): true or false. Defaults to "true". + x (int, optional): horizontal text position from 0 to video width. Defaults to 0. + y (int, optional): vertical text position from 0 to video heith. Defaults to 0. + message (str, optional): overlay text content. Defaults to "". + + Returns: + bool: True if successed. + """ + if message == '-': + message = "" + url = ( + self._prot + '://' + self._host + ':' + str(self._port) + + "/ISAPI/System/Video/inputs/channels/" + str(self._chan) + + "/overlays/text" + ) + xml = ''.join( + '' + + '' + + '' + + '1' + + '' + enabled + '' + + '' + str(x) + '' + + '' + str(y) + '' + + '' + message + '' + + '' + + '' + ) + response = self.__call(url=url, method="PUT", contenttype="text/xml", contentdata=xml) + if response != 'ERROR': + logging.debug(msg='\n' + response + '\n') + return True + else: + return False + + +class Sensor(Connect): + """Representing sensor connected to remote host. + The class inherits the necessary connection methods of the Connect class + """ + def __init__( + self, + hostname: str, username: str, userpass: str, + nodetype: str, nodename: str, + hostport: int = 22 + ) -> None: + """Object constructor. + + Args: + hostname (str): sensor's remote host hostname or ip address. + username (str): sensor's remote host username. + userpass (str): sensor's remote host password. + nodetype (str): 'ds18b20' or other sensor type. + nodename (str): 28-1a2b3c4d5e6f (ds18b20 example). + hostport (int, optional): sensor's remote host connection port. Defaults to 22. + """ + self._host = hostname + self._port = hostport + self._user = username + self._pswd = userpass + self._type = nodetype + self._node = nodename + + def __call(self, command: str) -> str: + """Send request to sensor's remote host. + + Args: + command (str): command to poll the sensor. + + Returns: + str: sensor's remote host response content. + """ + return self.ssh_commands( + command=command, + hostname=self._host, port=self._port, + username=self._user, password=self._pswd + ) + + # pylint: disable=W0718 + def __temperature(self, nodename: str) -> str: + """Preparating request for ds18b20 sensor type. + + Args: + nodename (str): 28-1a2b3c4d5e6f (ds18b20 example). + + Returns: + str: formatted string with temperature in Celsius. + """ + command = 'cat /sys/bus/w1/devices/' + nodename + '/temperature' + response = self.__call(command=command) + if response != 'ERROR': + try: + temperature = str(int(response)//1000) + "'C" + return temperature + except Exception as error: + logging.debug( + msg='' + + '\n' + 'host: ' + self._host + ':' + str(self._port) + + '\n' + 'user: ' + self._user + + '\n' + 'pass: ' + self._pswd + + '\n' + 'command: ' + command + + '\n' + 'error: ' + str(error) + ) + return 'ERROR' + + def value(self) -> str: + """Public method to get sensor value. + + Returns: + str: sensor value. + """ + if self._type == 'ds18b20': + return self.__temperature(nodename=self._node) + + +class Wordpress(Connect): + """Set of methods (functions) for Wordpress API. + Reference: https://developer.wordpress.org/rest-api/reference/ + + Args: + Connect (_type_): class with 'http' method. + """ + def __init__( + self, + hostname: str, + username: str, + password: str + ): + """Object constructor. + + Args: + hostname (str, optional): www.wordpress.site. + username (str, optional): wordpress username. + password (str, optional): wordpress passwrod. + """ + if Do.args_valid(locals(), self.__init__.__annotations__): + self._host = hostname + self._user = username + self._pass = password + self.api_event = 'https://' + self._host + '/wp-json/tribe/events/v1/events' + self.api_media = 'https://' + self._host + '/wp-json/wp/v2/media' + self.api_pages = 'https://' + self._host + '/wp-json/wp/v2/pages' + self.url_files = 'https://' + self._host + '/wp-content/uploads' + + def event_create( + self, + title: str, + slug: str, + date_start: str, + date_end: str, + date_publish: str = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), + all_day: bool = True, + description: str = None + ) -> dict: + """Create event by 'wp-json' and 'The Events Calendar'. + + Args: + title (str, optional): event title. + slug (str, optional): event slug. + date_start (str, optional): '2022-12-31T23:59:59' format. + date_end (str, optional): '2022-12-31T23:59:59' format. + date_publish (_type_, optional): '2022-12-31T23:59:59' format. Defaults to now. + all_day (bool, optional): all day event duration flag. Defaults to True. + description (str, optional): event body. Defaults to None. + + Raises: + ValueError: date formate is not 2022-12-31T23:59:59. + ValueError: description can't be empty. + + Returns: + dict: {'success':bool,'result':'http/url/to/event'}. + """ + if Do.args_valid(locals(), self.event_create.__annotations__): + pattern = "^([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2})+$" + if ( + not re.fullmatch(pattern, date_start) or + not re.fullmatch(pattern, date_end) or + not re.fullmatch(pattern, date_publish) + ): + raise ValueError("date formate is not 2022-12-31T23:59:59") + if description == '': + raise ValueError("description can't be empty") + + event_json = { + "title": title, + "slug": slug, + "date": date_publish, + "start_date": date_start, + "end_date": date_end, + "all_day": str(all_day), + "description": description + } + response = self.http( + url=self.api_event, + method='POST', + username=self._user, + password=self._pass, + authtype='basic', + contenttype='application/json; charset=UTF-8', + contentdata=json.dumps(event_json, indent=2) + ) + logging.debug( + msg="" + + "\n" + "event API response: " + + "\n" + json.dumps(json.loads(response['result']), indent=2) + ) + if response['success']: + for key, val in json.loads(response['result']).items(): + if key == "url": + logging.info('event created: %s', val) + return {"success": True, "result": val} + else: + logging.warning("event didn't create") + return {"success": False, "result": "ERROR"} + + def media_search( + self, + media_name: (str, type(None)) = None, + media_type: (str, type(None)) = None + ) -> dict: + """Search uploaded media by 'wp-json'. + + Args: + media_name (str, type, optional): results matching a string. Defaults to None. + media_type (str, type, optional): application,image,video,audio,text. Defaults to None. + + Returns: + dict: {'success':bool,'result':['list/of/link/to/media']} + """ + if Do.args_valid(locals(), self.media_search.__annotations__): + url = self.api_media + '?per_page=100' + if media_name: + url = url + '&search=' + media_name + if (media_type == 'application' or + media_type == 'image' or + media_type == 'video' or + media_type == 'audio' or + media_type == 'text' + ): + url = url + '&media_type=' + media_type + + media_list = [] + response = self.http(url=url, method='GET') + logging.debug( + msg="" + + "\n" + "media API response: " + + "\n" + json.dumps(json.loads(response['result']), indent=2) + ) + if response['success']: + for media in json.loads(response['result']): + media_list.append(media['guid']['rendered']) + return {"success": True, "result": media_list} + else: + logging.warning("media didn't list") + return {"success": False, "result": "ERROR"} + + def media_upload( + self, + mediafile: str, + mediatype: str, + aliasfile: str = '' + ) -> dict: + """Upload media by 'wp-json'. + + Args: + mediafile (str, optional): path to file. + mediatype (str, optional): 'image/jpeg', 'video/mp4', etc. + aliasfile (str, optional): uploaded media name. Defaults to original file. + + Raises: + FileExistsError: mediafile is not exist. + + Returns: + dict: {'success':bool,'result':'http/url/to/media'}. + """ + if Do.args_valid(locals(), self.media_upload.__annotations__): + if not path.exists(mediafile): + raise FileExistsError(mediafile + " is not exist") + else: + with open(mediafile, mode='rb') as file: + mediadata = file.read() + if aliasfile == '': + aliasfile = path.basename(mediafile) + + response = self.http( + url=self.api_media, + method='POST', + username=self._user, + password=self._pass, + authtype='basic', + contenttype=mediatype, + contentdata=mediadata, + headers={ + "Accept": "application/json", + 'Content-Disposition': 'attachment; filename=' + aliasfile, + 'Cache-Control': 'no-cache' + } + ) + logging.debug( + msg="" + + "\n" + "media API response: " + + "\n" + json.dumps(json.loads(response['result']), indent=2) + ) + if response['success']: + for key, val in json.loads(response['result']).items(): + if key == "source_url": + logging.info('media uploaded: %s', val) + return {"success": True, "result": val} + else: + logging.warning("media didn't upload") + return {"success": False, "result": "ERROR"} + + def pages_read( + self, + page_id: int + ) -> dict: + """Read page by 'wp-json'. + + Args: + page_id (int): unique identifier for the page. + + Returns: + dict: {'success':bool,'result':'page data'} + """ + if Do.args_valid(locals(), self.pages_read.__annotations__): + page_link = self.api_pages + '/' + str(page_id) + response = self.http(url=page_link) + if response['success']: + logging.debug( + msg="" + + "\n" + "wp page API response: " + + "\n" + json.dumps(json.loads(response['result']), indent=2) + ) + return {"success": True, "result": response['result']} + else: + logging.warning("wp page didn't read") + return {"success": False, "result": "ERROR"} + + def pages_update( + self, + page_id: int, + content: str + ) -> dict: + """Update page by 'wp-json'. + + Args: + page_id (int): unique identifier for the page. + content (str): the content for the page. + + Returns: + dict: {'success':bool,'result':'http/url/to/page'} + """ + if Do.args_valid(locals(), self.pages_update.__annotations__): + page_link = self.api_pages + '/' + str(page_id) + page_json = { + "content": content + } + response = self.http( + url=page_link, + method='POST', + username=self._user, + password=self._pass, + authtype='basic', + contenttype='application/json; charset=UTF-8', + contentdata=json.dumps(page_json) + ) + logging.debug( + msg="" + + "\n" + "wp page API response: " + + "\n" + json.dumps(json.loads(response['result']), indent=2) + ) + if response['success']: + for key, val in json.loads(response['result']).items(): + if key == "link": + logging.info(msg="wp page " + str(page_id) + " updated: " + val) + return {"success": True, "result": val} + else: + logging.warning("wp page didn't update") + return {"success": False, "result": "ERROR"} + + +class Telegram(): + """Set of methods (functions) for Telegram Bot API. + Reference: https://core.telegram.org/bots/api#available-methods + """ + def __init__(self, token: str): + """Object constructor. + + Args: + token (str): Telegram Bot API access token. + """ + if Do.args_valid(locals(), self.__init__.__annotations__): + self._token = token + self.api_root = 'https://api.telegram.org' + self.api_path = self.api_root + '/bot' + self._token + + def send_message(self, chat: str, text: str, parse_mode: str = 'HTML') -> dict: + """Send text message. + + Args: + chat (str): unique identifier for the target chat or username of the target channel. + text (str): text of the message to be sent, 1-4096 characters after entities parsing. + parse_mode (str, optional): 'HTML', 'Markdown', 'MarkdownV2'. Defaults to 'HTML'. + + Returns: + dict: {"success":bool,"result":"API response" or "ERROR"} + """ + if Do.args_valid(locals(), self.send_message.__annotations__): + url=self.api_path + '/sendMessage' + data = { + "chat_id": chat, + "text": text, + "parse_mode": parse_mode, + "disable_notification": True + } + response = requests.post(url=url, json=data, timeout=15) + if response.status_code == 200: + response = response.json() + logging.info(msg="" + + "message '" + + str(response['result']['message_id']) + + "' sent to telegram chat " + + str(chat) + ) + return {'success': True, 'result': response} + else: + logging.warning(msg="message didn't send to telegram chat " + str(chat)) + return {'success': False, 'result': response} + + def delete_message(self, chat: str, message_id: int) -> dict: + """Delete message. + + Args: + chat (str): unique identifier for the target chat or username of the target channel. + message_id (int): identifier of the message to delete. + + Returns: + dict: {"success":bool,"result":"API response" or "ERROR"} + """ + if Do.args_valid(locals(), self.delete_message.__annotations__): + url=self.api_path + '/deleteMessage' + data = {"chat_id": chat, "message_id": message_id} + response = requests.post(url=url, json=data, timeout=15) + if response.status_code == 200: + response = response.json() + logging.info(msg="" + + "message '" + str(message_id) + "' deleted from telegram chat " + + str(chat) + ) + return {'success': True, 'result': response} + else: + logging.warning(msg="" + + "message '" + str(message_id) + "' didn't deleted from telegram chat " + + str(chat) + ) + return {'success': False, 'result': response} + + def __send_media( + self, + chat: str, + media_path: str, + media_type: str, + caption: (str, type(None)), + parse_mode: str, + disable_notification: bool, + additional_url_param: (str, type(None)) + ) -> dict: + """Send media by api.telegram.org. + + Args: + chat (str): unique identifier for the target chat or username of the target channel. + media_path (str): /local/path/to/file, https://url/to/file, file_id=EXISTFILEID. + media_type (str): 'document', 'photo', 'video', 'audio'. + caption (str, None): media caption less 1024 characters. + parse_mode (str): caption 'HTML', 'Markdown', 'MarkdownV2' parse mode. + disable_notification (bool): send silently. + additional_url_param (str, None): example: '&duration=30&width=960&height=540'. + + Raises: + ValueError: "'media_type' value is wrong" + + Returns: + dict: {'success':bool,'result':response}. + """ + if Do.args_valid(locals(), self.__send_media.__annotations__): + if ( + media_type == 'document' or + media_type == 'photo' or + media_type == 'video' or + media_type == 'audio' + ): + url = self.api_path + '/send' + media_type + '?chat_id=' + chat + else: + raise ValueError("'media_type' value is wrong: " + media_type) + + if caption: + url = url + '&caption=' + caption + '&parse_mode=' + parse_mode + if disable_notification: + url = url + "&disable_notification=True" + if additional_url_param: + url = url + additional_url_param + if re.match("^(?:http://|https://|file_id=)", media_path): + media_path = media_path.replace('file_id=', '') + response = requests.post( + url=url + "&" + media_type + "=" + media_path, + timeout=60 + ) + if response.status_code == 200: + response = response.json() + if media_type == 'photo': + file_id = response['result'][media_type][-1]['file_id'] + else: + file_id = response['result'][media_type]['file_id'] + logging.info(msg="" + + media_type + + " '" + + str(file_id) + + "' sent to telegram chat " + + chat + ) + return {'success': True, 'result': response} + else: + response = requests.post( + url=url, + files={media_type: open(media_path, "rb")}, + timeout=60 + ) + if response.status_code == 200: + response = response.json() + if media_type == 'photo': + file_id = response['result'][media_type][-1]['file_id'] + else: + file_id = response['result'][media_type]['file_id'] + logging.info(msg="" + + media_type + + " '" + + str(file_id) + + "' sent to telegram chat " + + chat + ) + return {'success': True, 'result': response} + logging.warning( + msg=media_type + " " + media_path + " didn't send to telegram chat " + str(chat) + ) + return {'success': False, 'result': response} + + def send_document( + self, + chat: str, + document: str, + caption: (str, type(None)) = None, + parse_mode: str = 'HTML', + disable_notification: bool = True + ) -> dict: + """Send document. See self.__send_media(). + """ + if Do.args_valid(locals(), self.send_document.__annotations__): + return self.__send_media( + chat=chat, + media_path=document, + media_type='document', + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + additional_url_param=None + ) + + def send_photo( + self, + chat: str, + photo: str, + caption: (str, type(None)) = None, + parse_mode: str = 'HTML', + disable_notification: bool = True + ) -> dict: + """Send photo. See self.__send_media(). + """ + if Do.args_valid(locals(), self.send_photo.__annotations__): + return self.__send_media( + chat=chat, + media_path=photo, + media_type='photo', + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + additional_url_param=None + ) + + def send_video( + self, + chat: str, + video: str, + width: (int, type(None)) = None, + height: (int, type(None)) = None, + duration: (int, type(None)) = None, + caption: (str, type(None)) = None, + parse_mode: str = 'HTML', + disable_notification: bool = True + ) -> dict: + """Send video. See self.__send_media(). + """ + if Do.args_valid(locals(), self.send_video.__annotations__): + + if width or height or duration: + additional_url_param = '' + if width: + additional_url_param += '&width=' + str(width) + if height: + additional_url_param += '&height=' + str(height) + if duration: + additional_url_param += '&duration=' + str(duration) + else: + additional_url_param = None + + return self.__send_media( + chat=chat, + media_path=video, + media_type='video', + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + additional_url_param=additional_url_param + ) + + def send_audio( + self, + chat: str, + audio: str, + caption: (str, type(None)) = None, + parse_mode: str = 'HTML', + disable_notification: bool = True + ) -> dict: + """Send audio. See self.__send_media(). + """ + if Do.args_valid(locals(), self.send_audio.__annotations__): + return self.__send_media( + chat=chat, + media_path=audio, + media_type='audio', + caption=caption, + parse_mode=parse_mode, + disable_notification=disable_notification, + additional_url_param=None + ) + + def send_mediagroup( + self, + chat: str, + media: dict, + caption: (str, type(None)) = None, + parse_mode: str = 'HTML', + disable_notification: bool = True + ) -> dict: + """Send media group of photo, video, audio, documents. + + Args: + chat (str): unique identifier for the target chat or username of the target channel. + media (dict): { + name:{'type':'photo',path:'https://url/to/file',caption:text}, + name:{'type':'video',path:'/local/path/to/file',caption:text}, + name:{'type':'audio',path:'file_id=EXISTFILEID',caption:text}, + }. + caption (str, type, optional): media caption less 1024 characters. Defaults to None. + parse_mode (str): caption 'HTML', 'Markdown', 'MarkdownV2' parse mode. + disable_notification (bool, optional): send silently. Defaults to True. + + Returns: + dict: {'success':bool,'result':response}. + """ + if Do.args_valid(locals(), self.send_mediagroup.__annotations__): + url=self.api_path + '/sendMediaGroup' + files = {} + group = [] + + for media_name in media.keys(): + if re.match("^(?:http://|https://|file_id=)", media[media_name]['path']): + files[media_name] = None + media_source = media[media_name]['path'].replace('file_id=', '') + else: + with open(media[media_name]['path'], mode='rb') as file: + files[media_name] = file.read() + media_source = "attach://" + media_name + + if not caption and media[media_name]['caption']: + media_caption = media[media_name]['caption'] + else: + media_caption = '' + + group.append({ + "type": media[media_name]['type'], + "media": media_source, + "caption": media_caption + } + ) + + if caption: + group[0]['caption'] = caption + group[0]['parse_mode'] = parse_mode + + data = { + 'chat_id': chat, + 'media': json.dumps(group), + "disable_notification": disable_notification + } + + response = requests.post(url=url, data=data, files=files, timeout=300) + if response.status_code == 200: + response = response.json() + logging.info(msg="" + + "mediagroup '" + + str(response['result'][0]['media_group_id']) + + "' sent to telegram chat " + + str(chat) + ) + return {'success': True, 'result': response} + logging.warning(msg="mediagroup didn't send to telegram chat " + str(chat)) + return {'success': False, 'result': response} + + +class Sequence: + """Sequence handling. + """ + @staticmethod + # pylint: disable=W0718 + def run( + device: HikISAPI, sensors: dict, sequence: dict, + records_root_path: str = None, + records_root_user: str = None, + records_root_pass: str = None + ) -> None: + """Sequences executor. + + Args: + device (HikISAPI): HikISAPI object. + sensors (dict): collection as key=sensorname:value=Sensor object. + sequence (dict): sequence steps collection. + records_root_path (str, optional): path (local|smb|ftp,sftp) to records directory. + Defaults to None. + records_root_user (str, optional): username if path on remote host. + Defaults to None. + records_root_pass (str, optional): password if path on remote host. + Defaults to None. + """ + for key, value in sequence.items(): + action = value.split(',')[0].strip() + x = value.split(',')[1].strip() + y = value.split(',')[2].strip() + z = value.split(',')[3].strip() + p = value.split(',')[4].strip() + s = value.split(',')[5].strip() + t = value.split(',')[6].strip() + w = value.split(',')[7].strip() + m = value.split(',')[8].strip() + if 'sensor-config:' in m: + sensor_name = m.split(':')[1].strip() + sensor_value = sensors[sensor_name].value() + if sensor_value != 'ERROR': + m = sensor_value + else: + m = '' + logging.info(msg='' + + ' action:' + key + ' = ' + action + + ',' + x + ',' + y + ',' + z + + ',' + p + ',' + s + ',' + t + + ',' + w + ',' + m + ) + if action == 'capabilities': + response = device.capabilities() + elif action == 'getcamerapos': + response = device.getcamerapos() + elif action == 'rebootcamera': + response = device.rebootcamera() + elif action == 'setptzmovyyu': + response = device.setptzmovyyu(speed=int(s)) + elif action == 'setptzmovyyd': + response = device.setptzmovyyd(speed=int(s)) + elif action == 'setptzmovxxl': + response = device.setptzmovxxl(speed=int(s)) + elif action == 'setptzmovxxr': + response = device.setptzmovxxr(speed=int(s)) + elif action == 'setptzmovzzi': + response = device.setptzmovzzi(speed=int(s)) + elif action == 'setptzmovzzo': + response = device.setptzmovzzo(speed=int(s)) + elif action == 'setptzpreset': + response = device.setptzpreset(preset=int(p), speed=int(s)) + elif action == 'setptztostop': + response = device.setptztostop() + elif action == 'setcamerapos': + response = device.setcamerapos(x=int(x), y=int(y), z=int(z)) + elif action == 'setcameramov': + response = device.setcameramov(x=int(x), y=int(y), z=int(z), t=t) + elif action == 'setmovtohome': + response = device.setmovtohome() + elif action == 'setposashome': + response = device.setposashome() + elif action == 'settextonosd': + response = device.settextonosd(x=int(x), y=int(y), message=m) + elif action == 'downloadjpeg': + records_root_temp = records_root_path + if records_root_temp != path.dirname(path.realpath(__file__)): + records_root_temp = path.dirname(path.realpath(__file__)) + sep + 'temp' + makedirs(records_root_temp, exist_ok=True) + dy = datetime.datetime.now().strftime('%Y') + dm = datetime.datetime.now().strftime('%m') + dv = datetime.datetime.now().strftime('%V') + dd = datetime.datetime.now().strftime('%d') + th = datetime.datetime.now().strftime('%H') + tm = datetime.datetime.now().strftime('%M') + ts = datetime.datetime.now().strftime('%S') + records_file_name = ( + key + '_' + dy + '-' + dm + '-' + dd + '_' + th + '.' + tm + '.' + ts + '.jpeg' + ) + if device.downloadjpeg( + x=int(x), + y=int(y), + dst_file=records_root_temp + sep + records_file_name + ): + hostname = 'localhost' + hostport, hosttype = None, None + username = records_root_user + userpass = records_root_pass + hostpath = records_root_path + if '://' in records_root_path: + hostname = records_root_path.split('/')[2] + hosttype = records_root_path.split('://')[0] + if hosttype == 'ftp': + hostport = 21 + if hosttype == 'sftp': + hostport = 22 + if hosttype == 'smb': + hostport = 445 + hostpath = records_root_path.replace(hosttype + '://' + hostname, '') + if '@' in hostname: + username = hostname.split('@')[0].split(':')[0] + userpass = hostname.split('@')[0].split(':')[1] + hostname = hostname.split('@')[1] + if ':' in hostname: + hostport = int(hostname.split(':')[1]) + hostname = hostname.split(':')[0] + if hosttype == 'ftp': + src_file = records_root_temp + sep + records_file_name + dst_file = ( + hostpath + + '/' + dy + '/' + dm + '/' + dv + '/' + dd + '/' + + records_file_name + ) + if Connect.ftp_put_file( + src_file=src_file, + dst_file=dst_file, + hostname=hostname, + username=username, + password=userpass + ): + try: + remove(src_file) + except OSError: + pass + elif hosttype == 'sftp': + src_file = records_root_temp + sep + records_file_name + dst_file = ( + hostpath + + '/' + dy + '/' + dm + '/' + dv + '/' + dd + '/' + + records_file_name + ) + response = Connect.ssh_put_file( + src_file=src_file, dst_file=dst_file, + hostname=hostname, port=hostport, + username=username, password=userpass) + if response != 'ERROR': + try: + remove(src_file) + except OSError: + pass + response = True + else: + response = False + else: + src_file = records_root_temp + sep + records_file_name + dst_file = ( + hostpath + + sep + dy + sep + dm + sep + dv + sep + dd + sep + + records_file_name) + try: + makedirs( + hostpath + sep + dy + sep + dm + sep + dv + sep + dd, + exist_ok=True + ) + replace(src=src_file, dst=dst_file) + response = True + except Exception as error: + logging.debug(msg='' + + '\n' + 'src_file: ' + src_file + + '\n' + 'dst_file: ' + dst_file + + '\n' + 'error: ' + str(error) + ) + response = False + else: + response = False + if w != '-' or float(w) != 0: + sleep(float(w)) + if response: + logging.info(msg=' result:' + key + ' = OK') + else: + logging.warning(msg='result:' + key + ' = ERROR') + + +class Convert: + """Convert handling. + """ + @staticmethod + # pylint: disable=W0612 + def run( + image_root_path: (str, list), + image_find_names: list, + video_dest_path: str, + video_dest_sufx: str, + video_scale_x: int, + video_scale_y: int, + video_framerate: int, + video_duration: int, + temp_path: str, + image_root_user: (str, type(None)) = None, + image_root_pass: (str, type(None)) = None, + video_dest_user: (str, type(None)) = None, + video_dest_pass: (str, type(None)) = None + ) -> None: + """Converting executor. + + Args: + image_root_path (str, list): source images path. + image_find_names (list): image names to search. + video_dest_path (str): path to destination video. + video_dest_sufx (str): destination video name suffix. + video_scale_x (int): destination video width. + video_scale_y (int): destination video height. + video_framerate (int): destination video frame per second. + video_duration (int): destination video duration. + temp_path (str): path to directory for temp files. + image_root_user (str, type, optional): username to source images ftp,sftp,smb path. + Defaults to None. + image_root_pass (str, type, optional): password to source images ftp,sftp,smb path. + Defaults to None. + video_dest_user (str, type, optional): username to destination video ftp,sftp,smb path. + Defaults to None. + video_dest_pass (str, type, optional): password to destination video ftp,sftp,smb path. + Defaults to None. + """ + if isinstance(image_root_path, str): + image_root_path = [image_root_path] + + temp_path = temp_path + sep + Do.random_string(8) + temp_files = [] + for name in image_find_names: + image_found = [] + for image_root in image_root_path: + if '://' in image_root: + image_hostname = image_root.split('/')[2] + image_hosttype = image_root.split('://')[0] + if image_hosttype == 'ftp': + image_hostport = 21 + if image_hosttype == 'sftp': + image_hostport = 22 + if image_hosttype == 'smb': + image_hostport = 445 + image_hostpath = image_root.replace(image_hosttype + '://' + image_hostname, '') + if '@' in image_hostname: + image_root_user = image_hostname.split('@')[0].split(':')[0] + image_root_pass = image_hostname.split('@')[0].split(':')[1] + image_hostname = image_hostname.split('@')[1] + if ':' in image_hostname: + image_hostport = int(image_hostname.split(':')[1]) + image_hostname = image_hostname.split(':')[0] + if image_hosttype == 'ftp': + image_found = image_found + Connect.ftp_file_search( + root_path=image_hostpath, + search_name=name, + hostname=image_hostname, + username=image_root_user, + password=image_root_pass + ) + makedirs(temp_path, exist_ok=True) + for image in image_found: + if Connect.ftp_get_file( + src_file=image, + dst_file=temp_path + sep + path.basename(image), + hostname=image_hostname, + username=image_root_user, + password=image_root_pass + ): + temp_files.append(temp_path + sep + path.basename(image)) + elif image_hosttype == 'sftp': + pass + else: + pass + + image_temp_list = temp_path + sep + 'convert.list' + image_amount = 0 + with open(image_temp_list, mode='w+', encoding='UTF-8') as converter_list: + for file in Do.file_search(root_path=temp_path, search_name=name): + converter_list.write("\nfile '" + file + "'") + image_amount += 1 + temp_files.append(image_temp_list) + + video_converted = name + '_' + video_dest_sufx + '.mp4' + video_temp_path = temp_path + sep + video_converted + video_conv_conf = ('' + + '-r ' + + str(image_amount) + '/' + str(video_duration) + + ' -f concat -safe 0 -i ' + + image_temp_list + + ' -c:v libx264 -vf scale=' + str(video_scale_x) + ':' + str(video_scale_y) + + ',fps=' + str(video_framerate) + ',format=yuv420p ' + + video_temp_path + + ' -y' + ) + + if FFmpeg.run(raw=video_conv_conf) == 0: + temp_files.append(video_temp_path) + + if '://' in video_dest_path: + video_hostname = video_dest_path.split('/')[2] + video_hosttype = video_dest_path.split('://')[0] + if video_hosttype == 'ftp': + video_hostport = 21 + if video_hosttype == 'sftp': + video_hostport = 22 + if video_hosttype == 'smb': + video_hostport = 445 + video_hostpath = video_dest_path.replace( + video_hosttype + '://' + video_hostname, + '' + ) + if '@' in image_hostname: + video_dest_user = video_hostname.split('@')[0].split(':')[0] + video_dest_pass = video_hostname.split('@')[0].split(':')[1] + video_hostname = video_hostname.split('@')[1] + if ':' in video_hostname: + video_hostport = int(video_hostname.split(':')[1]) + video_hostname = video_hostname.split(':')[0] + if video_hosttype == 'ftp': + if Connect.ftp_put_file( + src_file=video_temp_path, + dst_file=video_hostpath + '/' + video_converted, + hostname=video_hostname, + username=video_dest_user, + password=video_dest_pass + ): + pass + elif image_hosttype == 'sftp': + pass + else: + pass + + for temp_file in temp_files: + try: + remove(temp_file) + except OSError as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + try: + rmdir(temp_path) + except OSError as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + + +class Proc: + """Find a running process from Python. + """ + @classmethod + # pylint: disable=W0612 + def _list_windows(cls) -> list: + """Find all running process with wmi. + + Returns: + list: dictionaries with descriptions of found processes. + """ + execlist = [] + separate = b'\r\r\n' + out, err = Popen( + [ + 'wmic', 'process', 'get', + 'CommandLine,ExecutablePath,Name,ProcessId', + '/format:list' + ], + stdout=PIPE, + stderr=PIPE + ).communicate() + for line in out.split(separate + separate): + execpid, exename, exepath, cmdline = None, None, None, None + for subline in line.split(separate): + if b'ProcessId=' in subline: + execpid = subline.split(b'=')[1].decode('utf-8') + if b'Name=' in subline: + exename = subline.split(b'=')[1].decode('utf-8') + if b'ExecutablePath=' in subline: + exepath = subline.split(b'=')[1].decode('utf-8') + if b'CommandLine=' in subline: + cmdline = subline.split(b'=')[1].decode('utf-8') + if execpid and exename: + execlist.append( + { + 'execpid': execpid, + 'exename': exename, + 'exepath': exepath, + 'cmdline': cmdline + } + ) + return execlist + + @classmethod + # pylint: disable=W0612 + def _list_linux(cls) -> list: + """Find all running process with ps. + + Returns: + list: dictionaries with descriptions of found processes. + """ + execlist = [] + out, err = Popen( + [ + '/bin/ps', '-eo', 'pid,args' + ], + stdout=PIPE, + stderr=PIPE + ).communicate() + for line in out.splitlines(): + execpid = line.split()[0].decode('utf-8') + exepath = line.split()[1].decode('utf-8') + exename = path.basename(exepath) + cmdline = line.split(None, 1)[1].decode('utf-8') + if execpid and exename: + execlist.append( + { + 'execpid': execpid, + 'exename': exename, + 'exepath': exepath, + 'cmdline': cmdline + } + ) + return execlist + + @classmethod + def list_all(cls) -> list: + """Find all running process. + + Returns: + list: dictionaries with descriptions of found processes. + """ + if platform.startswith('linux') or platform.startswith('darwin'): + return cls._list_linux() + elif platform.startswith('win32'): + return cls._list_windows() + else: + return None + + @classmethod + # pylint: disable=W0150 + def search(cls, find: str, exclude: str = None) -> list: + """Find specified processes. + + Args: + find (str): find process pid, name or arguments. + exclude (str, optional): exclude process pid, name or arguments. Defaults to None. + + Returns: + list: dictionaries with descriptions of found processes. + """ + proc_found = [] + try: + for proc in cls.list_all(): + if exclude and ( + exclude in proc['execpid'] or + exclude in proc['exename'] or + exclude in proc['exepath'] or + exclude in proc['cmdline'] + ): + pass + elif ( + find in proc['execpid'] or + find in proc['exename'] or + find in proc['exepath'] or + find in proc['cmdline'] + ): + proc_found.append(proc) + except TypeError as ex: + print('ON', platform, 'PLATFORM', 'search ERROR:', ex) + finally: + if len(proc_found) == 0: + return None + else: + return proc_found + + @classmethod + def kill(cls, pid: int) -> None: + """Kill the process by means of the OS. + + Args: + pid (int): process ID. + """ + if platform.startswith('linux') or platform.startswith('darwin'): + Popen(['kill', '-s', 'SIGKILL', str(pid)]) + elif platform.startswith('win32'): + Popen(['taskkill', '/PID', str(pid), '/F']) + + +class FFmpeg: + """FFmpeg management from Python. + """ + @classmethod + def run( + cls, + src: (str, type(None)) = None, + dst: str = None, + fps: int = None, + preset: str = None, + raw: (str, type(None)) = None, + ffpath: str = None, + watchdog: bool = False, + watchsec: int = None, + onlyonce: bool = False + ) -> int: + """Running the installed ffmpeg. + + Args: + src (str, type, optional): sources urls, example: + 'rtsp://user:pass@host:554/Streaming/Channels/101, anull'. Defaults to None. + dst (str, optional): destination url, example: 'rtp://239.0.0.1:5554'. Defaults to None. + fps (int, optional): frame per second encoding output. Defaults to None. + preset (str, optional): 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p. Defaults to None. + raw (str, type, optional): custom ffmpeg parameters string. Defaults to None. + ffpath (str, optional): custom path to bin, example: /usr/bin/ffmpeg. Defaults to None. + watchdog (bool, optional): detect ffmpeg freeze and terminate. Defaults to False. + watchsec (int, optional): seconds to wait before watchdog terminates. Defaults to None. + onlyonce (bool, optional): detect ffmpeg running copy and terminate. Defaults to False. + + Returns: + int: ffmpeg return code + """ + if not raw: + process = ([] + + cls._bin(ffpath).split() + + cls._src(src).split() + + cls._preset(preset, fps).split() + + cls._dst(dst).split() + ) + else: + process = cls._bin(ffpath).split() + raw.split() + + if onlyonce and Proc.search(' '.join(process)): + print('Process already exist, exit...') + else: + logging.info(msg='Starting ' + ' '.join(process)) + with Popen(process, stdout=PIPE, stderr=STDOUT) as proc: + que = None + if watchdog: + que = Queue() + Process( + target=cls._watchdog, + args=(proc.pid, watchsec, que,), + daemon=True + ).start() + for line in proc.stdout: + if not que: + logging.debug(msg=line) + else: + que.put(line) + return proc.returncode + + @classmethod + def _bin(cls, ffpath: str, tool: str = 'ffmpeg') -> str: + """Returns the path to the bin depending on the OS. + + Args: + ffpath (str): custom path to bin. + tool (str, optional): 'ffmpeg', 'ffprobe'. Defaults to 'ffmpeg'. + + Returns: + str: path to bin or None, if path does not exist. + """ + faq = ( + '\n' + 'Main download page: https://ffmpeg.org/download.html\n' + '\n' + 'Install on Linux (Debian):\n' + '\tsudo apt install -y ffmpeg\n' + '\tTarget: /usr/bin/ffmpeg\n' + '\n' + 'Install on Windows:\n' + '\tDownload and extract: https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z\n' + '\tTarget: "%PROGRAMFILES%\\ffmpeg\\bin\\ffmpeg.exe"\n' + '\n' + 'Install on MacOS:\n' + '\tDownload and extract: https://evermeet.cx/ffmpeg/\n' + '\tTarget: /usr/bin/ffmpeg\n' + ) + if not ffpath: + if platform.startswith('linux') or platform.startswith('darwin'): + if tool == 'ffprobe': + ffpath = '/usr/bin/ffprobe' + else: + ffpath = '/usr/bin/ffmpeg' + elif platform.startswith('win32'): + if tool == 'ffprobe': + ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffprobe.exe" + else: + ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" + if path.exists(ffpath): + return ffpath + else: + print('ON', platform, 'PLATFORM', 'not found', tool, faq) + return None + + @classmethod + def _src(cls, sources: str) -> list: + """Parsing sources into ffmpeg format. + + Args: + sources (str): comma-separated list of sources in string format. + + Returns: + list: ffmpeg format list of sources. + """ + list_sources = [] + for src in sources.split(','): + src = src.strip() + if 'null' in src: + src = ' '.join(['-f lavfi -i', src]) + elif 'rtsp' in src: + src = ' '.join(['-rtsp_transport tcp -i', src]) + else: + src = ' '.join(['-stream_loop -1 -re -i', src]) + list_sources.append(src) + return ' '.join(list_sources) + + @classmethod + def _preset(cls, choice: str, fps: int) -> str: + """Parsing preset into ffmpeg format. + + Args: + choice (str): preset selection. + fps (int): frame per second encoding output. + + Returns: + str: ffmpeg format encoding parameters. + """ + tune = '-tune zerolatency' + video = '-c:v copy' + audio = '-c:a aac -b:a 128k' + width, height, kbps = None, None, None + if choice: + if choice == '240p': + width, height, kbps = 426, 240, 480 + if choice == '360p': + width, height, kbps = 640, 360, 720 + if choice == '480p': + width, height, kbps = 854, 480, 1920 + if choice == '720p': + width, height, kbps = 1280, 720, 3960 + if choice == '1080p': + width, height, kbps = 1920, 1080, 5940 + if choice == '1440p': + width, height, kbps = 2560, 1440, 12960 + if choice == '2160p': + width, height, kbps = 3840, 2160, 32400 + if width and height and kbps: + video = ''.join( + [ + '-vf scale=', + str(width), ':', str(height), + ',setsar=1:1' + ] + ) + video = ' '.join( + [ + video, + '-c:v libx264 -pix_fmt yuv420p -preset ultrafast' + ] + ) + if fps: + video = ' '.join([video, '-r', str(fps), '-g', str(fps * 2)]) + video = ' '.join([video, '-b:v', str(kbps) + 'k']) + return ' '.join([tune, video, audio]) + + @classmethod + def _dst(cls, destination: str) -> str: + """Parsing destination into ffmpeg format. + + Args: + destination (str): destination path or url. + + Returns: + str: ffmpeg format destination. + """ + container = '-f null' + stdout = '-v debug' # '-nostdin -nostats' # '-report' + if destination: + if 'rtmp' in destination: + container = '-f flv' + elif "rtp" in destination: + container = '-f rtp_mpegts' + else: + destination = '-' + return ' '.join([container, destination, stdout]) + + @classmethod + def _watchdog(cls, pid: int, sec: int, que: Queue = None) -> None: + """If no data arrives in the queue, kill the process. + + Args: + pid (int): process ID. + sec (int): seconds to wait for data. + que (Queue, optional): queue pointer. Defaults to None. + """ + if not sec: + sec = 5 + if que: + while True: + while not que.empty(): + print(que.get()) + sleep(sec) + if que.empty(): + Proc.kill(pid) + print('exit by watchdog') + break + exit() + + @classmethod + def probe( + cls, + target: (str, type(None)) = None, + raw: (str, type(None)) = None, + ffpath: str = None + ) -> (dict, bytes, None): + """Running the installed ffprobe. + + Args: + target (str, type, optional): media file path to probe. Defaults to None. + raw (str, type, optional): custom ffprobe parameters string. Defaults to None. + ffpath (str, optional): custom path to bin, example: /usr/bin/ffprobe. Defaults to None. + + Returns: + dict, bytes, None: ffprobe response or None. + """ + if not raw: + command = ([] + + cls._bin(ffpath=ffpath, tool='ffprobe').split() + + ('-i ' + target + + ' -v quiet -print_format json -show_format -show_programs -show_streams').split() + ) + else: + command = cls._bin(ffpath=ffpath, tool='ffprobe').split() + raw.split() + + with Popen(command, stdout=PIPE, stderr=STDOUT) as process: + result = process.communicate() + if process.returncode == 0 and not raw: + return json.loads(result[0].decode('utf-8')) + elif process.returncode == 0 and raw: + return result[0] + else: + return None + + +class Do(): + """Set of various methods (functions) for routine. + """ + @staticmethod + def args_valid(arguments: dict, annotations: dict) -> bool: + """Arguments type validating by annotations. + + Args: + arguments (dict): 'locals()' immediately after starting the function. + annotations (dict): function.name.__annotations__. + + Raises: + TypeError: type of argument is not equal type in annotation. + + Returns: + bool: True if argument types are valid. + """ + for var_name, var_type in annotations.items(): + if not var_name == 'return': + if not isinstance(arguments[var_name], var_type): + raise TypeError("" + + "type of '" + + var_name + + "' = " + + str(arguments[var_name]) + + " is not " + + str(var_type) + ) + return True + + @staticmethod + def date_calc( + target: datetime.date = datetime.datetime.now(), + amount: int = 0, + period: str = None + ) -> dict: + """Calculating start/end dates for period: day, week, month, year. + + Args: + target (datetime.date, optional): date in the calculation period. Defaults to now. + amount (int, optional): +/- periods. Defaults to 0. + period (str, optional): 'y'|'year','m'|'month','w'|'week','d'|'day'. Defaults to None. + + Raises: + ValueError: 'period' value is wrong. + + Returns: + dict: { + 'start':{'y':int,'m':int,'w':int,'d':int}, + 'end':{'y':int,'m':int,'w':int,'d':int} + }. + """ + if Do.args_valid(locals(), Do.date_calc.__annotations__): + date = {} + if not period: + raise ValueError("'period' value is wrong: " + "''") + elif period == 'd' or period == 'day': + delta = target + datetime.timedelta(days=amount) + target = delta + date['period'] = 'day' + elif period == 'w' or period == 'week': + delta = target + datetime.timedelta(weeks=amount) + target_week = str(delta.year) + '-W' + str(delta.isocalendar()[1]) + target = datetime.datetime.strptime(target_week + '-1', "%G-W%V-%u") + delta = target + datetime.timedelta(days=6) + date['period'] = 'week' + elif period == 'm' or period == 'month': + delta_month = (target.month + amount) % 12 + if not delta_month: + delta_month = 12 + delta_year = target.year + ((target.month) + amount - 1) // 12 + delta_days = calendar.monthrange(delta_year, delta_month)[1] + delta = target = target.replace( + year=delta_year, + month=delta_month, + day=1 + ) + delta = delta.replace( + year=delta_year, + month=delta_month, + day=delta_days + ) + date['period'] = 'month' + elif period == 'y' or period == 'year': + target = target.replace( + year=target.year + amount, + month=1, + day=1 + ) + delta = target.replace( + year=target.year, + month=12, + day=31 + ) + date['period'] = 'year' + else: + raise ValueError("'period' value is wrong: " + period) + date['start'] = { + 'y': target.year, + 'm': target.month, + 'w': target.isocalendar()[1], + 'd': target.day + } + date['end'] = { + 'y': delta.year, + 'm': delta.month, + 'w': delta.isocalendar()[1], + 'd': delta.day + } + return date + + @staticmethod + def random_string(length: int) -> str: + """Generate string from lowercase letters, uppercase letters, digits. + + Args: + length (int): string lenght. + + Returns: + str: random string. + """ + return ''.join(choice(ascii_letters + digits) for i in range(length)) + + @staticmethod + # pylint: disable=W0612 + def file_search(root_path: str, search_name: (str, type(None)) = None) -> list: + """Search files. + + Args: + root_path (str): where to search. + search_name (str, type, optional): full or partial filename for the filter. + Defaults to None. + + Returns: + list: list of found files. + """ + found_list = [] + if Do.args_valid(locals(), Do.file_search.__annotations__): + for root, dirs, files in walk(root_path, topdown=False): + for file in files: + if search_name: + if search_name in file: + found_list.append(path.join(path.realpath(root), file)) + else: + found_list.append(path.join(path.realpath(root), file)) + found_list.sort() + return found_list + + @staticmethod + def wp_routine_media( + wp: Wordpress, + targets_media_files: dict, + period: str, + amount: int, + page_id: int + ) -> dict: + """Custom Wordpress media routine - upload media, create media events, update media page. + + Args: + wp (Wordpress): wordpress object. + targets_media_files (dict): {'period':{'name':'local/path/to/file'}}. + period (str, optional): 'y','m','w','d'. + amount (int, optional): +/- periods. + page_id (int): unique identifier for the page. + + Returns: + dict: {'media upload': bool, 'event create': bool, 'pages update': bool} + """ + if Do.args_valid(locals(), Do.wp_routine_media.__annotations__): + default_media_links = { + "d": { + "point-01": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.dd_.mp4" + ), + "point-02": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mm_.dd_.mp4" + ), + "point-04": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mm_.dd_.mp4" + ), + "point-05": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mm_.dd_.mp4" + ), + "point-11": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mm_.dd_.mp4" + ), + "point-12": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.dd_.mp4" + ) + }, + "w": { + "point-01": "https://www.hmp.today/wp-content/uploads/2022/07/point-01_w.mp4", + "point-02": "https://www.hmp.today/wp-content/uploads/2022/07/point-02_w.mp4", + "point-04": "https://www.hmp.today/wp-content/uploads/2022/07/point-04_w.mp4", + "point-05": "https://www.hmp.today/wp-content/uploads/2022/07/point-05_w.mp4", + "point-11": "https://www.hmp.today/wp-content/uploads/2022/07/point-11_w.mp4", + "point-12": "https://www.hmp.today/wp-content/uploads/2022/07/point-12_w.mp4" + }, + "m": { + "point-01": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.mp4" + ), + "point-02": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mm_.mp4" + ), + "point-04": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mm_.mp4" + ), + "point-05": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mm_.mp4" + ), + "point-11": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mm_.mp4" + ), + "point-12": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.mp4" + ) + }, + "y": { + "point-01": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mp4" + ), + "point-02": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mp4" + ), + "point-04": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mp4" + ), + "point-05": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mp4" + ), + "point-11": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mp4" + ), + "point-12": ( + "https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mp4" + ) + } + } + current_media_links = default_media_links[period] + result = {} + + # media upload + result['media upload'] = True + for name, link in current_media_links.items(): + file_found = wp.media_search( + media_name=path.basename(targets_media_files[name]), + media_type='video' + ) + if file_found['success'] and len(file_found['result']) > 0: + logging.info( + msg="" + + "upload skipped, " + + targets_media_files[name] + + " found on site" + ) + targets_media_files.pop(name, None) + current_media_links[name] = file_found['result'][0] + else: + file_upload = wp.media_upload(targets_media_files[name], 'video/mp4') + if file_upload['success']: + current_media_links[name] = file_upload['result'] + else: + result['media upload'] = False + + # event create + result['event create'] = True + for name, link in current_media_links.items(): + description = ('' + + '
' + + '' + + '
' + ) + date = Do.date_calc(period=period, amount=amount) + date_start = ('' + + str(date['start']['y']) + + '-' + + str(date['start']['m']).zfill(2) + + '-' + + str(date['start']['d']).zfill(2) + + 'T00:00:00' + ) + date_end = ('' + + str(date['end']['y']) + + '-' + + str(date['end']['m']).zfill(2) + + '-' + + str(date['end']['d']).zfill(2) + + 'T23:59:59' + ) + if period == 'd': + slug = ('' + + name + + '_' + + str(date['start']['y']) + + '-' + + str(date['start']['m']).zfill(2) + + '-' + + str(date['start']['d']).zfill(2) + ) + title = ('' + + name + + ' ' + + str(date['start']['y']) + + '.' + + str(date['start']['m']).zfill(2) + + '.' + + str(date['start']['d']).zfill(2) + ) + if period == 'w': + slug = ('' + + name + + '_' + + str(date['start']['y']) + + '-w' + + str(date['start']['w']).zfill(2) + ) + title = ('' + + name + + ' ' + + str(date['start']['y']) + + '-w' + + str(date['start']['w']).zfill(2) + ) + if period == 'm': + slug = ('' + + name + + '_' + + str(date['start']['y']) + + '-' + + str(date['start']['m']).zfill(2) + ) + title = ('' + + name + + ' ' + + str(date['start']['y']) + + '.' + + str(date['start']['m']).zfill(2) + ) + if period == 'y': + slug = ('' + + name + + '_' + + str(date['start']['y']) + ) + title = ('' + + name + + ' ' + + str(date['start']['y']) + ) + + event_api_slug = wp.api_event + '/by-slug/' + slug + if current_media_links[name] != default_media_links[period][name]: + pass + elif Connect.http(event_api_slug)['success']: + logging.info(msg="event skipped, " + event_api_slug + " found on site") + else: + event_create = wp.event_create( + title=title, + slug=slug, + date_start=date_start, + date_end=date_end, + date_publish=date_start, + description=description + ) + if not event_create['success']: + result['event create'] = False + + # pages update + result['pages update'] = True + page_read = wp.pages_read(page_id) + if page_read['success']: + content = json.loads(page_read['result'])['content']['rendered'] + for name, link in current_media_links.items(): + if period == 'd': + reg_exp = ("" + + "_(?:[0-9]{4}|yyyy)" + + ".(?:[0-9]{2}|mm_)" + + ".(?:[0-9]{2}|dd_)(?:|-[0-9]).mp4" + ) + if period == 'w': + reg_exp = "(?:_[0-9]{4}-w[0-9]{2}|_w)(?:|-[0-9]).mp4" + if period == 'm': + reg_exp = "_(?:[0-9]{4}|yyyy).(?:[0-9]{2}|mm_)(?:|-[0-9]).mp4" + if period == 'y': + reg_exp = "_(?:[0-9]{4}|yyyy)(?:|-[0-9])" + + replace = 0 + new_str = link + pattern = wp.url_files + "/[0-9]{4}/[0-9]{2}/" + name + reg_exp + for old_str in re.findall(pattern, content): + if old_str == new_str: + logging.info( + msg="" + + "page replace skipped, " + + new_str + + " found on page" + ) + else: + content = content.replace(old_str, new_str) + replace += 1 + logging.info(msg="page replace" + old_str + " to " + new_str) + + if replace > 0: + page_update = wp.pages_update(page_id = page_id, content = content) + result['pages update'] = page_update['success'] + + return result + + @staticmethod + # pylint: disable=W0612,W0511 + def tg_routine_media( + tg: Telegram, + targets_media_files: dict, + period: str, + amount: int, + chat: str + ) -> dict: + """Custom Telegram media routine - send mediagroup. + + Args: + tg (Telegram): telegram object + targets_media_files (dict): {'period':{'name':'local/path/to/file'}}. + period (str): 'y','m','w','d'. + amount (int): +/- periods. + chat (str): unique identifier for the target chat or username of the target channel. + + Raises: + ValueError: filename is not local file. + + Returns: + dict: {'success':bool,'result':response}. + """ + if Do.args_valid(locals(), Do.tg_routine_media.__annotations__): + default_caption = ("" + + "`period:` yyyy.mm.dd\n" + + "`source:` https://www.hmp.today/media\n" + + "`stream:` https://youtu.be/ikB20ML5oyo" + ) + group = { + "cover": + { + "type": "photo", + "path": ('' + + 'https://www.hmp.today/wp-content/uploads/' + + '2021/02/site-slider_hmp-qr_bwg-3840x1705-1.png' + ), + "caption": "source: https://www.hmp.today" + } + } + tmp_files = [] + + date = Do.date_calc(period=period, amount=amount) + if period == 'd': + caption_date = ('' + + str(date['start']['y']) + + '.' + + str(date['start']['m']).zfill(2) + + '.' + + str(date['start']['d']).zfill(2) + ) + if period == 'w': + caption_date = ('' + + str(date['start']['y']) + + '-w' + + str(date['start']['w']).zfill(2) + ) + if period == 'm': + caption_date = ('' + + str(date['start']['y']) + + '.' + + str(date['start']['m']).zfill(2) + ) + if period == 'y': + caption_date = ('' + + str(date['start']['y']) + ) + current_caption = default_caption.replace('yyyy.mm.dd', caption_date) + + for media_name, media_path in targets_media_files[period].items(): + if re.match("^(?:http://|https://|file_id=)", media_path): + # todo: check remote file size + raise ValueError(media_name + ' is not local file') + else: + tg_limit_video_size = 50000000 + src_video_info = FFmpeg.probe(target=media_path) + if src_video_info: + cur_video_file = media_path + cur_video_duration = int(float(src_video_info['format']['duration'])) + cur_video_fps = int( + src_video_info['streams'][0]['avg_frame_rate'].split('/')[0] + ) + cur_video_width = int(src_video_info['streams'][0]['width']) + cur_video_height = int(src_video_info['streams'][0]['height']) + + if stat(media_path).st_size >= tg_limit_video_size: + tmp_video_bitrate = int( + tg_limit_video_size*0.9/1000*8/cur_video_duration + ) + tmp_video_file = media_path.replace('.mp4', '_compressed.mp4') + cur_video_width = int(cur_video_width/2) + cur_video_height = int(cur_video_height/2) + + compressing_params = ' '.join( + [ + '-i', media_path, + '-c:v libx264 -b:v', str(tmp_video_bitrate) + 'k', + '-vf scale=' + + str(cur_video_width) + ':' + str(cur_video_height) + + ',fps=' + str(cur_video_fps) + + ',format=yuv420p -preset veryslow', + tmp_video_file, '-y -loglevel quiet -stats' + ] + ) + if FFmpeg.run(raw=compressing_params) == 0: + logging.info(msg=media_path + " comressed to " + tmp_video_file) + tmp_files.append(tmp_video_file) + cur_video_file = tmp_video_file + + response_upload = tg.send_video( + chat=chat, + video=cur_video_file, + width=cur_video_width, + height=cur_video_height, + duration=cur_video_duration + ) + if response_upload: + response_delete = tg.delete_message( + chat=chat, + message_id=response_upload['result']['result']['message_id'] + ) + group[media_name] = { + "type": "video", + "path": ( + 'file_id=' + + response_upload['result']['result']['video']['file_id'] + ) + } + + response_result = tg.send_mediagroup( + chat=chat, + media=group, + caption=current_caption, + parse_mode='Markdown' + ) + for file in tmp_files: + if remove(file): + logging.info(msg="deleted " + file) + return response_result + + +if __name__ == "__main__": + time_start = datetime.datetime.now() + + args = ArgumentParser( + prog='cctv-scheduler', + description='Hikvision PTZ IP-Camera management.', + epilog='Dependencies: ' + '- Python 3 (tested version 3.9.5), ' + '- Python 3 modules: paramiko ' + ) + args.add_argument('--config', type=str, default=path.splitext(__file__)[0] + '.conf', + required=False, + help='custom configuration file path') + args.add_argument('-b', '--broadcast', action='store_true', required=False, + help='streaming media to destination') + args.add_argument('-s', '--sequences', action='store_true', required=False, + help='run sequences from config file') + args.add_argument('-c', '--converter', action='store_true', required=False, + help='convert JPEG collection to MP4') + args.add_argument('-p', '--publisher', action='store_true', required=False, + help='publish content from templates') + args.add_argument('-d', '--day', type=int, default=0, required=False, + help=('day in amount of days from today, ' + + 'for which the publication or conversion is made') + ) + args.add_argument('-w', '--week', type=int, default=0, required=False, + help=('week in amount of weeks from today, ' + + 'for which the publication or conversion is made') + ) + args.add_argument('-m', '--month', type=int, default=0, required=False, + help=('month in amount of months from today, ' + + 'for which the publication or conversion is made') + ) + args.add_argument('-y', '--year', type=int, default=0, required=False, + help=('year in amount of years from today, ' + + 'for which the publication or conversion is made') + ) + args = vars(args.parse_args()) + + log_root = path.dirname(path.realpath(__file__)) + log_level = 'INFO' + temp_path = path.dirname(path.realpath(__file__)) + sep + 'temp' + if path.exists(args['config']): + conf = Parse(parameters=args['config'], block='common') + if 'log_root' in conf.data: + log_root = conf.data['log_root'] + if 'log_level' in conf.data: + if conf.data['log_level'] == 'DEBUG': + log_level = logging.DEBUG + elif conf.data['log_level'] == 'INFO': + log_level = logging.INFO + elif conf.data['log_level'] == 'WARNING': + log_level = logging.WARNING + elif conf.data['log_level'] == 'ERROR': + log_level = logging.ERROR + elif conf.data['log_level'] == 'CRITICAL': + log_level = logging.CRITICAL + logging.basicConfig( + format='%(asctime)s %(levelname)s: %(message)s', + datefmt='%Y-%m-%d_%H.%M.%S', + handlers=[ + logging.FileHandler( + filename=log_root + sep + path.splitext(path.basename(__file__))[0] + '.log', + mode='a' + ), + logging.StreamHandler() + ], + level=log_level + ) + logging.getLogger("paramiko").setLevel(logging.WARNING) + + if 'temp_path' in conf.data: + temp_path = conf.data['temp_path'] + + if args['broadcast']: + logging.info(msg='Starting streaming media to destination') + broadcasts = {} + conf = Parse(parameters=args['config'], block='enable-broadcast') + for key, value in conf.data.items(): + if value == 'true': + broadcast_config = Parse( + parameters=args['config'], + block='broadcast-config:' + key + ).data + src = None + if 'src' in broadcast_config: + src = broadcast_config['src'] + dst = None + if 'dst' in broadcast_config: + dst = broadcast_config['dst'] + fps = None + if 'fps' in broadcast_config: + fps = broadcast_config['fps'] + preset = None + if 'preset' in broadcast_config: + preset = broadcast_config['preset'] + ffpath = None + if 'ffpath' in broadcast_config: + ffpath = broadcast_config['ffpath'] + watchdog = None + if 'watchdog' in broadcast_config: + watchdog = broadcast_config['watchdog'] + watchsec = None + if 'watchsec' in broadcast_config: + watchsec = int(broadcast_config['watchsec']) + onlyonce = None + if 'onlyonce' in broadcast_config: + onlyonce = broadcast_config['onlyonce'] + FFmpeg.run( + src=src, + dst=dst, + fps=fps, + preset=preset, + ffpath=ffpath, + watchdog=watchdog, + watchsec=watchsec, + onlyonce=onlyonce + ) + elif args['sequences']: + logging.info(msg='Starting PTZ sequences from config file') + sensors = {} + conf = Parse(parameters=args['config'], block='enable-sensor') + for key, value in conf.data.items(): + if value == 'true': + device_config = Parse( + parameters=args['config'], + block='sensor-config:' + key + ).data + device_entity = Sensor( + hostname=device_config['hostname'], + username=device_config['username'], + userpass=device_config['userpass'], + nodetype=device_config['nodetype'], + nodename=device_config['nodename'] + ) + sensors[key] = device_entity + + conf = Parse(parameters=args['config'], block='enable-sequences') + for key, value in conf.data.items(): + if value == 'true': + device_sequence = Parse( + parameters=args['config'], + block='camera-sequences:' + key + ).data + device_config = Parse( + parameters=args['config'], + block='camera-config:' + key + ).data + device_entity = HikISAPI( + hostname=device_config['hostname'], + username=device_config['username'], + userpass=device_config['userpass'] + ) + records_root_path = path.dirname(path.realpath(__file__)) + records_root_user = None + records_root_pass = None + if 'records_root_path' in device_config: + records_root_path = device_config['records_root_path'] + if 'records_root_user' in device_config: + records_root_user = device_config['records_root_user'] + if 'records_root_pass' in device_config: + records_root_pass = device_config['records_root_pass'] + Sequence.run( + device=device_entity, + sensors=sensors, + sequence=device_sequence, + records_root_path=records_root_path, + records_root_user=records_root_user, + records_root_pass=records_root_pass + ) + elif args['converter']: + if args['day'] or args['week'] or args['month'] or args['year']: + convert_period = None + convert_amount = None + video_duration = None + if args['day']: + convert_period = 'day' + convert_amount = args['day'] + if args['week']: + convert_period = 'week' + convert_amount = args['week'] + if args['month']: + convert_period = 'month' + convert_amount = args['month'] + if args['year']: + convert_period = 'year' + convert_amount = args['year'] + convert_date = Do.date_calc(period=convert_period, amount=convert_amount) + + logging.info(msg='Starting convert JPEG collection to MP4') + conf = Parse(parameters=args['config'], block='enable-convert') + for key, value in conf.data.items(): + if value == 'true': + convert_config = Parse( + parameters=args['config'], + block='convert-config:' + key + ).data + image_root_path = path.dirname(path.realpath(__file__)) + image_root_user = None + image_root_pass = None + image_find_names = None + if 'image_root_path' in convert_config: + image_root_path = convert_config['image_root_path'] + if 'image_root_user' in convert_config: + image_root_user = convert_config['image_root_user'] + if 'image_root_pass' in convert_config: + image_root_pass = convert_config['image_root_pass'] + image_find_names = [] + for name in convert_config['image_find_names'].split(','): + image_find_names.append(name.strip()) + + if convert_date['period'] == 'day': + image_root_path = ('' + + image_root_path + '/' + + str(convert_date['start']['y']) + '/' + + str(convert_date['start']['m']).zfill(2) + '/' + + str(convert_date['start']['w']).zfill(2) + '/' + + str(convert_date['start']['d']).zfill(2) + ) + video_dest_sufx = ('' + + str(convert_date['start']['y']) + '.' + + str(convert_date['start']['m']).zfill(2) + '.' + + str(convert_date['start']['d']).zfill(2) + ) + video_duration = 1 + if convert_date['period'] == 'week': + if convert_date['start']['m'] == convert_date['end']['m']: + image_root_path = ('' + + image_root_path + '/' + + str(convert_date['start']['y']) + '/' + + str(convert_date['start']['m']).zfill(2) + '/' + + str(convert_date['start']['w']).zfill(2) + ) + else: + image_root_path = [ + ('' + + image_root_path + '/' + + str(convert_date['start']['y']) + '/' + + str(convert_date['start']['m']).zfill(2) + '/' + + str(convert_date['start']['w']).zfill(2) + ), + ('' + + image_root_path + '/' + + str(convert_date['end']['y']) + '/' + + str(convert_date['end']['m']).zfill(2) + '/' + + str(convert_date['end']['w']).zfill(2) + ) + ] + video_dest_sufx = ('' + + str(convert_date['start']['y']) + '-w' + + str(convert_date['start']['w']).zfill(2) + ) + video_duration = 7 + if convert_date['period'] == 'month': + image_root_path = ('' + + image_root_path + '/' + + str(convert_date['start']['y']) + '/' + + str(convert_date['start']['m']).zfill(2) + ) + video_dest_sufx = ('' + + str(convert_date['start']['y']) + '.' + + str(convert_date['start']['m']).zfill(2) + ) + video_duration = 30 + if convert_date['period'] == 'year': + image_root_path = ('' + + image_root_path + '/' + + str(convert_date['start']['y']) + ) + video_dest_sufx = ('' + + str(convert_date['start']['y']) + ) + video_duration = 360 + + video_dest_path = path.dirname(path.realpath(__file__)) + video_dest_user = None + video_dest_pass = None + if 'video_dest_path' in convert_config: + video_dest_path = convert_config['video_dest_path'] + if 'video_dest_user' in convert_config: + video_dest_user = convert_config['video_dest_user'] + if 'video_dest_pass' in convert_config: + video_dest_pass = convert_config['video_dest_pass'] + + Convert.run( + image_root_path=image_root_path, + image_root_user=image_root_user, + image_root_pass=image_root_pass, + image_find_names=image_find_names, + video_dest_path=video_dest_path, + video_dest_sufx=video_dest_sufx, + video_dest_user=video_dest_user, + video_dest_pass=video_dest_pass, + video_scale_x=int(convert_config['video_scale_x']), + video_scale_y=int(convert_config['video_scale_y']), + video_framerate=int(convert_config['video_framerate']), + video_duration=video_duration, + temp_path=temp_path + ) + else: + logging.info(msg='Period was not selected. Exit.') + elif args['publisher']: + logging.info(msg='Starting publish content from templates') + else: + logging.info(msg='Start arguments was not selected. Exit.') + + time_execute = datetime.datetime.now() - time_start + logging.info(msg='execution time is ' + str(time_execute) + '. Exit.') diff --git a/publisher-template-page-1007.xml b/archive/0.4/publisher-template-page-1007.xml similarity index 100% rename from publisher-template-page-1007.xml rename to archive/0.4/publisher-template-page-1007.xml diff --git a/publisher.conf b/archive/0.4/publisher.conf similarity index 100% rename from publisher.conf rename to archive/0.4/publisher.conf diff --git a/publisher.sh b/archive/0.4/publisher.sh similarity index 100% rename from publisher.sh rename to archive/0.4/publisher.sh diff --git a/cctv-scheduler.conf b/cctv-scheduler.conf index 743d0d3..9ddc2b4 100644 --- a/cctv-scheduler.conf +++ b/cctv-scheduler.conf @@ -19,7 +19,7 @@ camera.test.local = true [enable-sequences] -# List the sequence/camera block names. Only blocks with the TRUE value will be used. +# List the sequence camera block names. Only blocks with the TRUE value will be used. camera.test.local = true @@ -33,6 +33,11 @@ sensor.test.local = true camera.test.local = true +[enable-publish] +# List the publish block names. Only blocks with the TRUE value will be used. +camera.test.local = true + + [broadcast-config:camera.test.local] # Broadcast parameter description block always starts with "broadcast-config:". src = rtsp://user:pass@192.168.254.253:554/Streaming/Channels/101,http://radio.fm:8000/stream.mp3 @@ -78,7 +83,7 @@ username = user userpass = pass # If a record directory on a remote host is used, a username and password must be specified. # Supported protocols: -# FTP, SFTP. +# FTP. records_root_path = ftp://192.168.254.254/Records/camera.test.local records_root_user = user records_root_pass = pass @@ -119,8 +124,9 @@ step999 = rebootcamera, -, -, -, -, -, -, [convert-config:camera.test.local] # Converter parameter description block always starts with "convert-config:". +# image_find_names = step071, image-01, image-02 -# If a image root or destination video directories on a remote host is used, a username and password must be specified. +# If image root or destination video directories on a remote host is used, username and password must be specified. # Supported protocols: # FTP. image_root_path = ftp://192.168.254.254/Records/camera.test.local @@ -133,4 +139,25 @@ video_dest_pass = pass video_scale_x = 1920 video_scale_y = 1080 -video_framerate = 25 \ No newline at end of file +video_framerate = 25 + + +[publish-config:camera.test.local] +# Publisher parameter description block always starts with "publish-config:". +video_find_names = step071, image-01, image-02 +# If a video directory on a remote host is used, a username and password must be specified. +# Supported protocols: +# FTP. +video_root_path = ftp://192.168.254.254/Downloads +video_root_user = user +video_root_pass = pass + +wp_enabled = true +wp_site_name = www.site.name +wp_user_name = user +wp_user_pass = pass +wp_update_page_id = 4848 + +tg_enabled = true +tg_api_key = TELEGRAM_API_KEY +tg_chat_id = @blackhole \ No newline at end of file diff --git a/cctv-scheduler.py b/cctv-scheduler.py index cd5df83..56a2748 100644 --- a/cctv-scheduler.py +++ b/cctv-scheduler.py @@ -16,7 +16,7 @@ from multiprocessing import Process, Queue from os import environ, makedirs, path, remove, replace, rmdir, sep, stat, walk from string import ascii_letters, digits from subprocess import Popen, PIPE, STDOUT -from sys import platform +from sys import modules, platform from time import sleep import requests from paramiko import SSHClient, AutoAddPolicy @@ -181,6 +181,403 @@ class Parse: return result +class Proc: + """Find a running process from Python. + """ + @classmethod + # pylint: disable=W0612 + def _list_windows(cls) -> list: + """Find all running process with wmi. + + Returns: + list: dictionaries with descriptions of found processes. + """ + execlist = [] + separate = b'\r\r\n' + out, err = Popen( + [ + 'wmic', 'process', 'get', + 'CommandLine,ExecutablePath,Name,ProcessId', + '/format:list' + ], + stdout=PIPE, + stderr=PIPE + ).communicate() + for line in out.split(separate + separate): + execpid, exename, exepath, cmdline = None, None, None, None + for subline in line.split(separate): + if b'ProcessId=' in subline: + execpid = subline.split(b'=')[1].decode('utf-8') + if b'Name=' in subline: + exename = subline.split(b'=')[1].decode('utf-8') + if b'ExecutablePath=' in subline: + exepath = subline.split(b'=')[1].decode('utf-8') + if b'CommandLine=' in subline: + cmdline = subline.split(b'=')[1].decode('utf-8') + if execpid and exename: + execlist.append( + { + 'execpid': execpid, + 'exename': exename, + 'exepath': exepath, + 'cmdline': cmdline + } + ) + return execlist + + @classmethod + # pylint: disable=W0612 + def _list_linux(cls) -> list: + """Find all running process with ps. + + Returns: + list: dictionaries with descriptions of found processes. + """ + execlist = [] + out, err = Popen( + [ + '/bin/ps', '-eo', 'pid,args' + ], + stdout=PIPE, + stderr=PIPE + ).communicate() + for line in out.splitlines(): + execpid = line.split()[0].decode('utf-8') + exepath = line.split()[1].decode('utf-8') + exename = path.basename(exepath) + cmdline = line.split(None, 1)[1].decode('utf-8') + if execpid and exename: + execlist.append( + { + 'execpid': execpid, + 'exename': exename, + 'exepath': exepath, + 'cmdline': cmdline + } + ) + return execlist + + @classmethod + def list_all(cls) -> list: + """Find all running process. + + Returns: + list: dictionaries with descriptions of found processes. + """ + if platform.startswith('linux') or platform.startswith('darwin'): + return cls._list_linux() + elif platform.startswith('win32'): + return cls._list_windows() + else: + return None + + @classmethod + # pylint: disable=W0150 + def search(cls, find: str, exclude: str = None) -> list: + """Find specified processes. + + Args: + find (str): find process pid, name or arguments. + exclude (str, optional): exclude process pid, name or arguments. Defaults to None. + + Returns: + list: dictionaries with descriptions of found processes. + """ + proc_found = [] + try: + for proc in cls.list_all(): + if exclude and ( + exclude in proc['execpid'] or + exclude in proc['exename'] or + exclude in proc['exepath'] or + exclude in proc['cmdline'] + ): + pass + elif ( + find in proc['execpid'] or + find in proc['exename'] or + find in proc['exepath'] or + find in proc['cmdline'] + ): + proc_found.append(proc) + except TypeError as ex: + print('ON', platform, 'PLATFORM', 'search ERROR:', ex) + finally: + if len(proc_found) == 0: + return None + else: + return proc_found + + @classmethod + def kill(cls, pid: int) -> None: + """Kill the process by means of the OS. + + Args: + pid (int): process ID. + """ + if platform.startswith('linux') or platform.startswith('darwin'): + Popen(['kill', '-s', 'SIGKILL', str(pid)]) + elif platform.startswith('win32'): + Popen(['taskkill', '/PID', str(pid), '/F']) + + +class FFmpeg: + """FFmpeg management from Python. + """ + @classmethod + def run( + cls, + src: (str, type(None)) = None, + dst: str = None, + fps: int = None, + preset: str = None, + raw: (str, type(None)) = None, + ffpath: str = None, + watchdog: bool = False, + watchsec: int = None, + onlyonce: bool = False + ) -> int: + """Running the installed ffmpeg. + + Args: + src (str, type, optional): sources urls, example: + 'rtsp://user:pass@host:554/Streaming/Channels/101, anull'. Defaults to None. + dst (str, optional): destination url, example: 'rtp://239.0.0.1:5554'. Defaults to None. + fps (int, optional): frame per second encoding output. Defaults to None. + preset (str, optional): 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p. Defaults to None. + raw (str, type, optional): custom ffmpeg parameters string. Defaults to None. + ffpath (str, optional): custom path to bin, example: /usr/bin/ffmpeg. Defaults to None. + watchdog (bool, optional): detect ffmpeg freeze and terminate. Defaults to False. + watchsec (int, optional): seconds to wait before watchdog terminates. Defaults to None. + onlyonce (bool, optional): detect ffmpeg running copy and terminate. Defaults to False. + + Returns: + int: ffmpeg return code + """ + if not raw: + process = ([] + + cls._bin(ffpath).split() + + cls._src(src).split() + + cls._preset(preset, fps).split() + + cls._dst(dst).split() + ) + else: + process = cls._bin(ffpath).split() + raw.split() + + if onlyonce and Proc.search(' '.join(process)): + print('Process already exist, exit...') + else: + logging.info(msg='Starting ' + ' '.join(process)) + with Popen(process, stdout=PIPE, stderr=STDOUT) as proc: + que = None + if watchdog: + que = Queue() + Process( + target=cls._watchdog, + args=(proc.pid, watchsec, que,), + daemon=True + ).start() + for line in proc.stdout: + if not que: + logging.debug(msg=line) + else: + que.put(line) + return proc.returncode + + @classmethod + def _bin(cls, ffpath: str, tool: str = 'ffmpeg') -> str: + """Returns the path to the bin depending on the OS. + + Args: + ffpath (str): custom path to bin. + tool (str, optional): 'ffmpeg', 'ffprobe'. Defaults to 'ffmpeg'. + + Returns: + str: path to bin or None, if path does not exist. + """ + faq = ( + '\n' + 'Main download page: https://ffmpeg.org/download.html\n' + '\n' + 'Install on Linux (Debian):\n' + '\tsudo apt install -y ffmpeg\n' + '\tTarget: /usr/bin/ffmpeg\n' + '\n' + 'Install on Windows:\n' + '\tDownload and extract: https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z\n' + '\tTarget: "%PROGRAMFILES%\\ffmpeg\\bin\\ffmpeg.exe"\n' + '\n' + 'Install on MacOS:\n' + '\tDownload and extract: https://evermeet.cx/ffmpeg/\n' + '\tTarget: /usr/bin/ffmpeg\n' + ) + if not ffpath: + if platform.startswith('linux') or platform.startswith('darwin'): + if tool == 'ffprobe': + ffpath = '/usr/bin/ffprobe' + else: + ffpath = '/usr/bin/ffmpeg' + elif platform.startswith('win32'): + if tool == 'ffprobe': + ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffprobe.exe" + else: + ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" + if path.exists(ffpath): + return ffpath + else: + print('ON', platform, 'PLATFORM', 'not found', tool, faq) + return None + + @classmethod + def _src(cls, sources: str) -> list: + """Parsing sources into ffmpeg format. + + Args: + sources (str): comma-separated list of sources in string format. + + Returns: + list: ffmpeg format list of sources. + """ + list_sources = [] + for src in sources.split(','): + src = src.strip() + if 'null' in src: + src = ' '.join(['-f lavfi -i', src]) + elif 'rtsp' in src: + src = ' '.join(['-rtsp_transport tcp -i', src]) + else: + src = ' '.join(['-stream_loop -1 -re -i', src]) + list_sources.append(src) + return ' '.join(list_sources) + + @classmethod + def _preset(cls, choice: str, fps: int) -> str: + """Parsing preset into ffmpeg format. + + Args: + choice (str): preset selection. + fps (int): frame per second encoding output. + + Returns: + str: ffmpeg format encoding parameters. + """ + tune = '-tune zerolatency' + video = '-c:v copy' + audio = '-c:a aac -b:a 128k' + width, height, kbps = None, None, None + if choice: + if choice == '240p': + width, height, kbps = 426, 240, 480 + if choice == '360p': + width, height, kbps = 640, 360, 720 + if choice == '480p': + width, height, kbps = 854, 480, 1920 + if choice == '720p': + width, height, kbps = 1280, 720, 3960 + if choice == '1080p': + width, height, kbps = 1920, 1080, 5940 + if choice == '1440p': + width, height, kbps = 2560, 1440, 12960 + if choice == '2160p': + width, height, kbps = 3840, 2160, 32400 + if width and height and kbps: + video = ''.join( + [ + '-vf scale=', + str(width), ':', str(height), + ',setsar=1:1' + ] + ) + video = ' '.join( + [ + video, + '-c:v libx264 -pix_fmt yuv420p -preset ultrafast' + ] + ) + if fps: + video = ' '.join([video, '-r', str(fps), '-g', str(fps * 2)]) + video = ' '.join([video, '-b:v', str(kbps) + 'k']) + return ' '.join([tune, video, audio]) + + @classmethod + def _dst(cls, destination: str) -> str: + """Parsing destination into ffmpeg format. + + Args: + destination (str): destination path or url. + + Returns: + str: ffmpeg format destination. + """ + container = '-f null' + stdout = '-v debug' # '-nostdin -nostats' # '-report' + if destination: + if 'rtmp' in destination: + container = '-f flv' + elif "rtp" in destination: + container = '-f rtp_mpegts' + else: + destination = '-' + return ' '.join([container, destination, stdout]) + + @classmethod + def _watchdog(cls, pid: int, sec: int, que: Queue = None) -> None: + """If no data arrives in the queue, kill the process. + + Args: + pid (int): process ID. + sec (int): seconds to wait for data. + que (Queue, optional): queue pointer. Defaults to None. + """ + if not sec: + sec = 5 + if que: + while True: + while not que.empty(): + print(que.get()) + sleep(sec) + if que.empty(): + Proc.kill(pid) + print('exit by watchdog') + break + exit() + + @classmethod + def probe( + cls, + target: (str, type(None)) = None, + raw: (str, type(None)) = None, + ffpath: str = None + ) -> (dict, bytes, None): + """Running the installed ffprobe. + + Args: + target (str, type, optional): media file path to probe. Defaults to None. + raw (str, type, optional): custom ffprobe parameters string. Defaults to None. + ffpath (str, optional): custom path to bin, example: /usr/bin/ffprobe. Defaults to None. + + Returns: + dict, bytes, None: ffprobe response or None. + """ + if not raw: + command = ([] + + cls._bin(ffpath=ffpath, tool='ffprobe').split() + + ('-i ' + target + + ' -v quiet -print_format json -show_format -show_programs -show_streams').split() + ) + else: + command = cls._bin(ffpath=ffpath, tool='ffprobe').split() + raw.split() + + with Popen(command, stdout=PIPE, stderr=STDOUT) as process: + result = process.communicate() + if process.returncode == 0 and not raw: + return json.loads(result[0].decode('utf-8')) + elif process.returncode == 0 and raw: + return result[0] + else: + return None + + class Connect: # pylint: disable=W0105 """Set of connection methods (functions) for various protocols. @@ -1936,7 +2333,6 @@ class Convert: if isinstance(image_root_path, str): image_root_path = [image_root_path] - temp_path = temp_path + sep + Do.random_string(8) temp_files = [] for name in image_find_names: image_found = [] @@ -2050,401 +2446,152 @@ class Convert: logging.debug(msg='\n' + 'error: ' + str(error)) -class Proc: - """Find a running process from Python. +class Publish: + """Publish handling. """ - @classmethod + @staticmethod # pylint: disable=W0612 - def _list_windows(cls) -> list: - """Find all running process with wmi. - - Returns: - list: dictionaries with descriptions of found processes. - """ - execlist = [] - separate = b'\r\r\n' - out, err = Popen( - [ - 'wmic', 'process', 'get', - 'CommandLine,ExecutablePath,Name,ProcessId', - '/format:list' - ], - stdout=PIPE, - stderr=PIPE - ).communicate() - for line in out.split(separate + separate): - execpid, exename, exepath, cmdline = None, None, None, None - for subline in line.split(separate): - if b'ProcessId=' in subline: - execpid = subline.split(b'=')[1].decode('utf-8') - if b'Name=' in subline: - exename = subline.split(b'=')[1].decode('utf-8') - if b'ExecutablePath=' in subline: - exepath = subline.split(b'=')[1].decode('utf-8') - if b'CommandLine=' in subline: - cmdline = subline.split(b'=')[1].decode('utf-8') - if execpid and exename: - execlist.append( - { - 'execpid': execpid, - 'exename': exename, - 'exepath': exepath, - 'cmdline': cmdline - } - ) - return execlist - - @classmethod - # pylint: disable=W0612 - def _list_linux(cls) -> list: - """Find all running process with ps. - - Returns: - list: dictionaries with descriptions of found processes. - """ - execlist = [] - out, err = Popen( - [ - '/bin/ps', '-eo', 'pid,args' - ], - stdout=PIPE, - stderr=PIPE - ).communicate() - for line in out.splitlines(): - execpid = line.split()[0].decode('utf-8') - exepath = line.split()[1].decode('utf-8') - exename = path.basename(exepath) - cmdline = line.split(None, 1)[1].decode('utf-8') - if execpid and exename: - execlist.append( - { - 'execpid': execpid, - 'exename': exename, - 'exepath': exepath, - 'cmdline': cmdline - } - ) - return execlist - - @classmethod - def list_all(cls) -> list: - """Find all running process. - - Returns: - list: dictionaries with descriptions of found processes. - """ - if platform.startswith('linux') or platform.startswith('darwin'): - return cls._list_linux() - elif platform.startswith('win32'): - return cls._list_windows() - else: - return None - - @classmethod - # pylint: disable=W0150 - def search(cls, find: str, exclude: str = None) -> list: - """Find specified processes. - - Args: - find (str): find process pid, name or arguments. - exclude (str, optional): exclude process pid, name or arguments. Defaults to None. - - Returns: - list: dictionaries with descriptions of found processes. - """ - proc_found = [] - try: - for proc in cls.list_all(): - if exclude and ( - exclude in proc['execpid'] or - exclude in proc['exename'] or - exclude in proc['exepath'] or - exclude in proc['cmdline'] - ): - pass - elif ( - find in proc['execpid'] or - find in proc['exename'] or - find in proc['exepath'] or - find in proc['cmdline'] - ): - proc_found.append(proc) - except TypeError as ex: - print('ON', platform, 'PLATFORM', 'search ERROR:', ex) - finally: - if len(proc_found) == 0: - return None - else: - return proc_found - - @classmethod - def kill(cls, pid: int) -> None: - """Kill the process by means of the OS. - - Args: - pid (int): process ID. - """ - if platform.startswith('linux') or platform.startswith('darwin'): - Popen(['kill', '-s', 'SIGKILL', str(pid)]) - elif platform.startswith('win32'): - Popen(['taskkill', '/PID', str(pid), '/F']) - - -class FFmpeg: - """FFmpeg management from Python. - """ - @classmethod def run( - cls, - src: (str, type(None)) = None, - dst: str = None, - fps: int = None, - preset: str = None, - raw: (str, type(None)) = None, - ffpath: str = None, - watchdog: bool = False, - watchsec: int = None, - onlyonce: bool = False - ) -> int: - """Running the installed ffmpeg. + video_root_path: (str, list), + video_find_names: list, + temp_path: str, + publish_period: str, + publish_amount: int, + video_root_user: (str, type(None)) = None, + video_root_pass: (str, type(None)) = None, + wp_site_name: (str, type(None)) = None, + wp_user_name: (str, type(None)) = None, + wp_user_pass: (str, type(None)) = None, + wp_update_page_id: (int, type(None)) = None, + tg_api_key: (str, type(None)) = None, + tg_chat_id: (str, type(None)) = None + ) -> None: + """Publishing executor. Args: - src (str, type, optional): sources urls, example: - 'rtsp://user:pass@host:554/Streaming/Channels/101, anull'. Defaults to None. - dst (str, optional): destination url, example: 'rtp://239.0.0.1:5554'. Defaults to None. - fps (int, optional): frame per second encoding output. Defaults to None. - preset (str, optional): 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p. Defaults to None. - raw (str, type, optional): custom ffmpeg parameters string. Defaults to None. - ffpath (str, optional): custom path to bin, example: /usr/bin/ffmpeg. Defaults to None. - watchdog (bool, optional): detect ffmpeg freeze and terminate. Defaults to False. - watchsec (int, optional): seconds to wait before watchdog terminates. Defaults to None. - onlyonce (bool, optional): detect ffmpeg running copy and terminate. Defaults to False. - - Returns: - int: ffmpeg return code + video_root_path (str, list): source video path. + video_find_names (list): video names to search. + temp_path (str): path to directory for temp files. + publish_period (str): +/- periods. + publish_amount (int): 'y'|'year','m'|'month','w'|'week','d'|'day'. + video_root_user (str, None, optional): username to source video ftp,sftp,smb path. + Defaults to None. + video_root_pass (str, None, optional): password to source video ftp,sftp,smb path. + Defaults to None. + wp_site_name (str, None, optional): www.wordpress.site. + Defaults to None. + wp_user_name (str, None, optional): wordpress username. + Defaults to None. + wp_user_pass (str, None, optional): wordpress password. + Defaults to None. + wp_update_page_id (int, None, optional): unique identifier for the page. + Defaults to None. + tg_api_key (str, None, optional): Telegram Bot API access token. + Defaults to None. + tg_chat_id (str, None, optional): unique identifier for + the target chat or username of the target channel. + Defaults to None. """ - if not raw: - process = ([] - + cls._bin(ffpath).split() - + cls._src(src).split() - + cls._preset(preset, fps).split() - + cls._dst(dst).split() + if isinstance(video_root_path, str): + video_root_path = [video_root_path] + + publish_date = Do.date_calc(period=publish_period, amount=publish_amount) + if publish_date['period'] == 'day': + video_find_sufx = ('' + + str(publish_date['start']['y']) + '.' + + str(publish_date['start']['m']).zfill(2) + '.' + + str(publish_date['start']['d']).zfill(2) ) - else: - process = cls._bin(ffpath).split() + raw.split() - - if onlyonce and Proc.search(' '.join(process)): - print('Process already exist, exit...') - else: - logging.info(msg='Starting ' + ' '.join(process)) - with Popen(process, stdout=PIPE, stderr=STDOUT) as proc: - que = None - if watchdog: - que = Queue() - Process( - target=cls._watchdog, - args=(proc.pid, watchsec, que,), - daemon=True - ).start() - for line in proc.stdout: - if not que: - logging.debug(msg=line) - else: - que.put(line) - return proc.returncode - - @classmethod - def _bin(cls, ffpath: str, tool: str = 'ffmpeg') -> str: - """Returns the path to the bin depending on the OS. - - Args: - ffpath (str): custom path to bin. - tool (str, optional): 'ffmpeg', 'ffprobe'. Defaults to 'ffmpeg'. - - Returns: - str: path to bin or None, if path does not exist. - """ - faq = ( - '\n' - 'Main download page: https://ffmpeg.org/download.html\n' - '\n' - 'Install on Linux (Debian):\n' - '\tsudo apt install -y ffmpeg\n' - '\tTarget: /usr/bin/ffmpeg\n' - '\n' - 'Install on Windows:\n' - '\tDownload and extract: https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full.7z\n' - '\tTarget: "%PROGRAMFILES%\\ffmpeg\\bin\\ffmpeg.exe"\n' - '\n' - 'Install on MacOS:\n' - '\tDownload and extract: https://evermeet.cx/ffmpeg/\n' - '\tTarget: /usr/bin/ffmpeg\n' - ) - if not ffpath: - if platform.startswith('linux') or platform.startswith('darwin'): - if tool == 'ffprobe': - ffpath = '/usr/bin/ffprobe' - else: - ffpath = '/usr/bin/ffmpeg' - elif platform.startswith('win32'): - if tool == 'ffprobe': - ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffprobe.exe" - else: - ffpath = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" - if path.exists(ffpath): - return ffpath - else: - print('ON', platform, 'PLATFORM', 'not found', tool, faq) - return None - - @classmethod - def _src(cls, sources: str) -> list: - """Parsing sources into ffmpeg format. - - Args: - sources (str): comma-separated list of sources in string format. - - Returns: - list: ffmpeg format list of sources. - """ - list_sources = [] - for src in sources.split(','): - src = src.strip() - if 'null' in src: - src = ' '.join(['-f lavfi -i', src]) - elif 'rtsp' in src: - src = ' '.join(['-rtsp_transport tcp -i', src]) - else: - src = ' '.join(['-stream_loop -1 -re -i', src]) - list_sources.append(src) - return ' '.join(list_sources) - - @classmethod - def _preset(cls, choice: str, fps: int) -> str: - """Parsing preset into ffmpeg format. - - Args: - choice (str): preset selection. - fps (int): frame per second encoding output. - - Returns: - str: ffmpeg format encoding parameters. - """ - tune = '-tune zerolatency' - video = '-c:v copy' - audio = '-c:a aac -b:a 128k' - width, height, kbps = None, None, None - if choice: - if choice == '240p': - width, height, kbps = 426, 240, 480 - if choice == '360p': - width, height, kbps = 640, 360, 720 - if choice == '480p': - width, height, kbps = 854, 480, 1920 - if choice == '720p': - width, height, kbps = 1280, 720, 3960 - if choice == '1080p': - width, height, kbps = 1920, 1080, 5940 - if choice == '1440p': - width, height, kbps = 2560, 1440, 12960 - if choice == '2160p': - width, height, kbps = 3840, 2160, 32400 - if width and height and kbps: - video = ''.join( - [ - '-vf scale=', - str(width), ':', str(height), - ',setsar=1:1' - ] - ) - video = ' '.join( - [ - video, - '-c:v libx264 -pix_fmt yuv420p -preset ultrafast' - ] - ) - if fps: - video = ' '.join([video, '-r', str(fps), '-g', str(fps * 2)]) - video = ' '.join([video, '-b:v', str(kbps) + 'k']) - return ' '.join([tune, video, audio]) - - @classmethod - def _dst(cls, destination: str) -> str: - """Parsing destination into ffmpeg format. - - Args: - destination (str): destination path or url. - - Returns: - str: ffmpeg format destination. - """ - container = '-f null' - stdout = '-v debug' # '-nostdin -nostats' # '-report' - if destination: - if 'rtmp' in destination: - container = '-f flv' - elif "rtp" in destination: - container = '-f rtp_mpegts' - else: - destination = '-' - return ' '.join([container, destination, stdout]) - - @classmethod - def _watchdog(cls, pid: int, sec: int, que: Queue = None) -> None: - """If no data arrives in the queue, kill the process. - - Args: - pid (int): process ID. - sec (int): seconds to wait for data. - que (Queue, optional): queue pointer. Defaults to None. - """ - if not sec: - sec = 5 - if que: - while True: - while not que.empty(): - print(que.get()) - sleep(sec) - if que.empty(): - Proc.kill(pid) - print('exit by watchdog') - break - exit() - - @classmethod - def probe( - cls, - target: (str, type(None)) = None, - raw: (str, type(None)) = None, - ffpath: str = None - ) -> (dict, bytes, None): - """Running the installed ffprobe. - - Args: - target (str, type, optional): media file path to probe. Defaults to None. - raw (str, type, optional): custom ffprobe parameters string. Defaults to None. - ffpath (str, optional): custom path to bin, example: /usr/bin/ffprobe. Defaults to None. - - Returns: - dict, bytes, None: ffprobe response or None. - """ - if not raw: - command = ([] - + cls._bin(ffpath=ffpath, tool='ffprobe').split() - + ('-i ' + target - + ' -v quiet -print_format json -show_format -show_programs -show_streams').split() + if publish_date['period'] == 'week': + video_find_sufx = ('' + + str(publish_date['start']['y']) + '-w' + + str(publish_date['start']['w']).zfill(2) + ) + if publish_date['period'] == 'month': + video_find_sufx = ('' + + str(publish_date['start']['y']) + '.' + + str(publish_date['start']['m']).zfill(2) + ) + if publish_date['period'] == 'year': + video_find_sufx = ('' + + str(publish_date['start']['y']) ) - else: - command = cls._bin(ffpath=ffpath, tool='ffprobe').split() + raw.split() - with Popen(command, stdout=PIPE, stderr=STDOUT) as process: - result = process.communicate() - if process.returncode == 0 and not raw: - return json.loads(result[0].decode('utf-8')) - elif process.returncode == 0 and raw: - return result[0] - else: - return None + temp_path = temp_path + sep + Do.random_string(8) + temp_files = [] + video_files = {publish_date['period']:{}} + for name in video_find_names: + video_found = [] + for video_root in video_root_path: + if '://' in video_root: + video_hostname = video_root.split('/')[2] + video_hosttype = video_root.split('://')[0] + video_hostpath = video_root.replace(video_hosttype + '://' + video_hostname, '') + if '@' in video_hostname: + video_root_user = video_hostname.split('@')[0].split(':')[0] + video_root_pass = video_hostname.split('@')[0].split(':')[1] + video_hostname = video_hostname.split('@')[1] + if ':' in video_hostname: + video_hostport = int(video_hostname.split(':')[1]) + video_hostname = video_hostname.split(':')[0] + if video_hosttype == 'ftp': + video_found = video_found + Connect.ftp_file_search( + root_path=video_hostpath, + search_name=name + '_' + video_find_sufx + '.mp4', + hostname=video_hostname, + username=video_root_user, + password=video_root_pass + ) + makedirs(temp_path, exist_ok=True) + for video in video_found: + if Connect.ftp_get_file( + src_file=video, + dst_file=temp_path + sep + path.basename(video), + hostname=video_hostname, + username=video_root_user, + password=video_root_pass + ): + temp_files.append(temp_path + sep + path.basename(video)) + video_files[publish_period][name] = ( + temp_path + sep + path.basename(video) + ) + elif video_hosttype == 'sftp': + pass + else: + pass + + if tg_api_key: + tg = Telegram(tg_api_key) + Do.tg_routine_media( + tg=tg, + targets_media_files=video_files, + period=publish_date['period'], + amount=publish_amount, + chat=tg_chat_id + ) + if wp_site_name: + wp = Wordpress( + hostname=wp_site_name, + username=wp_user_name, + password=wp_user_pass + ) + Do.wp_routine_media( + wp=wp, + targets_media_files=video_files, + period=publish_date['period'], + amount=publish_amount, + page_id=wp_update_page_id + ) + + for temp_file in temp_files: + try: + remove(temp_file) + except OSError as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + try: + rmdir(temp_path) + except OSError as error: + logging.debug(msg='\n' + 'error: ' + str(error)) class Do(): @@ -2495,6 +2642,7 @@ class Do(): Returns: dict: { + 'period':day|week|month|year, 'start':{'y':int,'m':int,'w':int,'d':int}, 'end':{'y':int,'m':int,'w':int,'d':int} }. @@ -2617,7 +2765,7 @@ class Do(): """ if Do.args_valid(locals(), Do.wp_routine_media.__annotations__): default_media_links = { - "d": { + "day": { "point-01": ( "https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.dd_.mp4" ), @@ -2637,7 +2785,7 @@ class Do(): "https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.dd_.mp4" ) }, - "w": { + "week": { "point-01": "https://www.hmp.today/wp-content/uploads/2022/07/point-01_w.mp4", "point-02": "https://www.hmp.today/wp-content/uploads/2022/07/point-02_w.mp4", "point-04": "https://www.hmp.today/wp-content/uploads/2022/07/point-04_w.mp4", @@ -2645,7 +2793,7 @@ class Do(): "point-11": "https://www.hmp.today/wp-content/uploads/2022/07/point-11_w.mp4", "point-12": "https://www.hmp.today/wp-content/uploads/2022/07/point-12_w.mp4" }, - "m": { + "month": { "point-01": ( "https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.mp4" ), @@ -2665,7 +2813,7 @@ class Do(): "https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.mp4" ) }, - "y": { + "year": { "point-01": ( "https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mp4" ), @@ -2693,20 +2841,20 @@ class Do(): result['media upload'] = True for name, link in current_media_links.items(): file_found = wp.media_search( - media_name=path.basename(targets_media_files[name]), + media_name=path.basename(targets_media_files[period][name]), media_type='video' ) if file_found['success'] and len(file_found['result']) > 0: logging.info( msg="" + "upload skipped, " - + targets_media_files[name] + + targets_media_files[period][name] + " found on site" ) - targets_media_files.pop(name, None) + targets_media_files[period].pop(name, None) current_media_links[name] = file_found['result'][0] else: - file_upload = wp.media_upload(targets_media_files[name], 'video/mp4') + file_upload = wp.media_upload(targets_media_files[period][name], 'video/mp4') if file_upload['success']: current_media_links[name] = file_upload['result'] else: @@ -2737,7 +2885,7 @@ class Do(): + str(date['end']['d']).zfill(2) + 'T23:59:59' ) - if period == 'd': + if period == 'd' or period == 'day': slug = ('' + name + '_' @@ -2756,7 +2904,7 @@ class Do(): + '.' + str(date['start']['d']).zfill(2) ) - if period == 'w': + if period == 'w' or period == 'week': slug = ('' + name + '_' @@ -2771,7 +2919,7 @@ class Do(): + '-w' + str(date['start']['w']).zfill(2) ) - if period == 'm': + if period == 'm' or period == 'month': slug = ('' + name + '_' @@ -2786,7 +2934,7 @@ class Do(): + '.' + str(date['start']['m']).zfill(2) ) - if period == 'y': + if period == 'y' or period == 'year': slug = ('' + name + '_' @@ -2821,17 +2969,17 @@ class Do(): if page_read['success']: content = json.loads(page_read['result'])['content']['rendered'] for name, link in current_media_links.items(): - if period == 'd': + if period == 'd' or period == 'day': reg_exp = ("" + "_(?:[0-9]{4}|yyyy)" + ".(?:[0-9]{2}|mm_)" + ".(?:[0-9]{2}|dd_)(?:|-[0-9]).mp4" ) - if period == 'w': + if period == 'w' or period == 'week': reg_exp = "(?:_[0-9]{4}-w[0-9]{2}|_w)(?:|-[0-9]).mp4" - if period == 'm': + if period == 'm' or period == 'month': reg_exp = "_(?:[0-9]{4}|yyyy).(?:[0-9]{2}|mm_)(?:|-[0-9]).mp4" - if period == 'y': + if period == 'y' or period == 'year': reg_exp = "_(?:[0-9]{4}|yyyy)(?:|-[0-9])" replace = 0 @@ -2848,13 +2996,13 @@ class Do(): else: content = content.replace(old_str, new_str) replace += 1 - logging.info(msg="page replace" + old_str + " to " + new_str) + logging.info(msg="page replace " + old_str + " to " + new_str) if replace > 0: page_update = wp.pages_update(page_id = page_id, content = content) result['pages update'] = page_update['success'] - return result + return result @staticmethod # pylint: disable=W0612,W0511 @@ -2900,7 +3048,7 @@ class Do(): tmp_files = [] date = Do.date_calc(period=period, amount=amount) - if period == 'd': + if period == 'd' or period == 'day': caption_date = ('' + str(date['start']['y']) + '.' @@ -2908,19 +3056,19 @@ class Do(): + '.' + str(date['start']['d']).zfill(2) ) - if period == 'w': + if period == 'w' or period == 'week': caption_date = ('' + str(date['start']['y']) + '-w' + str(date['start']['w']).zfill(2) ) - if period == 'm': + if period == 'm' or period == 'month': caption_date = ('' + str(date['start']['y']) + '.' + str(date['start']['m']).zfill(2) ) - if period == 'y': + if period == 'y' or period == 'year': caption_date = ('' + str(date['start']['y']) ) @@ -3001,47 +3149,52 @@ class Do(): if __name__ == "__main__": time_start = datetime.datetime.now() - args = ArgumentParser( - prog='cctv-scheduler', - description='Hikvision PTZ IP-Camera management.', - epilog='Dependencies: ' - '- Python 3 (tested version 3.9.5), ' - '- Python 3 modules: paramiko ' - ) - args.add_argument('--config', type=str, default=path.splitext(__file__)[0] + '.conf', - required=False, - help='custom configuration file path') - args.add_argument('-b', '--broadcast', action='store_true', required=False, - help='streaming media to destination') - args.add_argument('-s', '--sequences', action='store_true', required=False, - help='run sequences from config file') - args.add_argument('-c', '--converter', action='store_true', required=False, - help='convert JPEG collection to MP4') - args.add_argument('-p', '--publisher', action='store_true', required=False, - help='publish content from templates') - args.add_argument('-d', '--day', type=int, default=0, required=False, - help=('day in amount of days from today, ' - + 'for which the publication or conversion is made') - ) - args.add_argument('-w', '--week', type=int, default=0, required=False, - help=('week in amount of weeks from today, ' - + 'for which the publication or conversion is made') - ) - args.add_argument('-m', '--month', type=int, default=0, required=False, - help=('month in amount of months from today, ' - + 'for which the publication or conversion is made') - ) - args.add_argument('-y', '--year', type=int, default=0, required=False, - help=('year in amount of years from today, ' - + 'for which the publication or conversion is made') - ) - args = vars(args.parse_args()) + if 'argparse' in modules: + args = ArgumentParser( + prog='cctv-scheduler', + description='Hikvision PTZ IP-Camera management.', + epilog='Dependencies: ' + '- Python 3 (tested version 3.9.5), ' + '- Python 3 modules: paramiko ' + ) + args.add_argument('--config', type=str, default=path.splitext(__file__)[0] + '.conf', + required=False, + help='custom configuration file path') + args.add_argument('-b', '--broadcast', action='store_true', required=False, + help='streaming media to destination') + args.add_argument('-s', '--sequences', action='store_true', required=False, + help='run sequences from config file') + args.add_argument('-c', '--converter', action='store_true', required=False, + help='convert JPEG collection to MP4') + args.add_argument('-p', '--publisher', action='store_true', required=False, + help='publish content from templates') + args.add_argument('-d', '--day', type=int, default=0, required=False, + help=('day in amount of days from today, ' + + 'for which the publication or conversion is made') + ) + args.add_argument('-w', '--week', type=int, default=0, required=False, + help=('week in amount of weeks from today, ' + + 'for which the publication or conversion is made') + ) + args.add_argument('-m', '--month', type=int, default=0, required=False, + help=('month in amount of months from today, ' + + 'for which the publication or conversion is made') + ) + args.add_argument('-y', '--year', type=int, default=0, required=False, + help=('year in amount of years from today, ' + + 'for which the publication or conversion is made') + ) + args = vars(args.parse_args()) + temp_path = path.dirname(path.realpath(__file__)) + sep + 'temp' log_root = path.dirname(path.realpath(__file__)) log_level = 'INFO' - temp_path = path.dirname(path.realpath(__file__)) + sep + 'temp' if path.exists(args['config']): + + # common conf = Parse(parameters=args['config'], block='common') + if 'temp_path' in conf.data: + temp_path = conf.data['temp_path'] if 'log_root' in conf.data: log_root = conf.data['log_root'] if 'log_level' in conf.data: @@ -3069,11 +3222,9 @@ if __name__ == "__main__": ) logging.getLogger("paramiko").setLevel(logging.WARNING) - if 'temp_path' in conf.data: - temp_path = conf.data['temp_path'] - if args['broadcast']: logging.info(msg='Starting streaming media to destination') + broadcasts = {} conf = Parse(parameters=args['config'], block='enable-broadcast') for key, value in conf.data.items(): @@ -3081,7 +3232,7 @@ if __name__ == "__main__": broadcast_config = Parse( parameters=args['config'], block='broadcast-config:' + key - ).data + ).data src = None if 'src' in broadcast_config: src = broadcast_config['src'] @@ -3106,7 +3257,8 @@ if __name__ == "__main__": onlyonce = None if 'onlyonce' in broadcast_config: onlyonce = broadcast_config['onlyonce'] - FFmpeg.run( + exit_code = None + exit_code = FFmpeg.run( src=src, dst=dst, fps=fps, @@ -3116,8 +3268,13 @@ if __name__ == "__main__": watchsec=watchsec, onlyonce=onlyonce ) + logging.info(msg='' + + 'Finished streaming ' + key + ' with exit code: ' + + str(exit_code) + ) elif args['sequences']: logging.info(msg='Starting PTZ sequences from config file') + sensors = {} conf = Parse(parameters=args['config'], block='enable-sensor') for key, value in conf.data.items(): @@ -3125,14 +3282,14 @@ if __name__ == "__main__": device_config = Parse( parameters=args['config'], block='sensor-config:' + key - ).data + ).data device_entity = Sensor( hostname=device_config['hostname'], username=device_config['username'], userpass=device_config['userpass'], nodetype=device_config['nodetype'], nodename=device_config['nodename'] - ) + ) sensors[key] = device_entity conf = Parse(parameters=args['config'], block='enable-sequences') @@ -3141,16 +3298,16 @@ if __name__ == "__main__": device_sequence = Parse( parameters=args['config'], block='camera-sequences:' + key - ).data + ).data device_config = Parse( parameters=args['config'], block='camera-config:' + key - ).data + ).data device_entity = HikISAPI( hostname=device_config['hostname'], username=device_config['username'], userpass=device_config['userpass'] - ) + ) records_root_path = path.dirname(path.realpath(__file__)) records_root_user = None records_root_pass = None @@ -3167,27 +3324,28 @@ if __name__ == "__main__": records_root_path=records_root_path, records_root_user=records_root_user, records_root_pass=records_root_pass - ) - elif args['converter']: - if args['day'] or args['week'] or args['month'] or args['year']: - convert_period = None - convert_amount = None - video_duration = None - if args['day']: - convert_period = 'day' - convert_amount = args['day'] - if args['week']: - convert_period = 'week' - convert_amount = args['week'] - if args['month']: - convert_period = 'month' - convert_amount = args['month'] - if args['year']: - convert_period = 'year' - convert_amount = args['year'] - convert_date = Do.date_calc(period=convert_period, amount=convert_amount) + ) + logging.info(msg='Finished PTZ sequence ' + key) + elif args['day'] or args['week'] or args['month'] or args['year']: + period = None + amount = None + if args['day']: + period = 'day' + amount = args['day'] + if args['week']: + period = 'week' + amount = args['week'] + if args['month']: + period = 'month' + amount = args['month'] + if args['year']: + period = 'year' + amount = args['year'] + period = Do.date_calc(period=period, amount=amount) + if args['converter']: logging.info(msg='Starting convert JPEG collection to MP4') + conf = Parse(parameters=args['config'], block='enable-convert') for key, value in conf.data.items(): if value == 'true': @@ -3209,66 +3367,66 @@ if __name__ == "__main__": for name in convert_config['image_find_names'].split(','): image_find_names.append(name.strip()) - if convert_date['period'] == 'day': + if period['period'] == 'day': image_root_path = ('' + image_root_path + '/' - + str(convert_date['start']['y']) + '/' - + str(convert_date['start']['m']).zfill(2) + '/' - + str(convert_date['start']['w']).zfill(2) + '/' - + str(convert_date['start']['d']).zfill(2) + + str(period['start']['y']) + '/' + + str(period['start']['m']).zfill(2) + '/' + + str(period['start']['w']).zfill(2) + '/' + + str(period['start']['d']).zfill(2) ) video_dest_sufx = ('' - + str(convert_date['start']['y']) + '.' - + str(convert_date['start']['m']).zfill(2) + '.' - + str(convert_date['start']['d']).zfill(2) + + str(period['start']['y']) + '.' + + str(period['start']['m']).zfill(2) + '.' + + str(period['start']['d']).zfill(2) ) video_duration = 1 - if convert_date['period'] == 'week': - if convert_date['start']['m'] == convert_date['end']['m']: + if period['period'] == 'week': + if period['start']['m'] == period['end']['m']: image_root_path = ('' + image_root_path + '/' - + str(convert_date['start']['y']) + '/' - + str(convert_date['start']['m']).zfill(2) + '/' - + str(convert_date['start']['w']).zfill(2) + + str(period['start']['y']) + '/' + + str(period['start']['m']).zfill(2) + '/' + + str(period['start']['w']).zfill(2) ) else: image_root_path = [ ('' + image_root_path + '/' - + str(convert_date['start']['y']) + '/' - + str(convert_date['start']['m']).zfill(2) + '/' - + str(convert_date['start']['w']).zfill(2) + + str(period['start']['y']) + '/' + + str(period['start']['m']).zfill(2) + '/' + + str(period['start']['w']).zfill(2) ), ('' + image_root_path + '/' - + str(convert_date['end']['y']) + '/' - + str(convert_date['end']['m']).zfill(2) + '/' - + str(convert_date['end']['w']).zfill(2) + + str(period['end']['y']) + '/' + + str(period['end']['m']).zfill(2) + '/' + + str(period['end']['w']).zfill(2) ) ] video_dest_sufx = ('' - + str(convert_date['start']['y']) + '-w' - + str(convert_date['start']['w']).zfill(2) + + str(period['start']['y']) + '-w' + + str(period['start']['w']).zfill(2) ) video_duration = 7 - if convert_date['period'] == 'month': + if period['period'] == 'month': image_root_path = ('' + image_root_path + '/' - + str(convert_date['start']['y']) + '/' - + str(convert_date['start']['m']).zfill(2) + + str(period['start']['y']) + '/' + + str(period['start']['m']).zfill(2) ) video_dest_sufx = ('' - + str(convert_date['start']['y']) + '.' - + str(convert_date['start']['m']).zfill(2) + + str(period['start']['y']) + '.' + + str(period['start']['m']).zfill(2) ) video_duration = 30 - if convert_date['period'] == 'year': + if period['period'] == 'year': image_root_path = ('' + image_root_path + '/' - + str(convert_date['start']['y']) + + str(period['start']['y']) ) video_dest_sufx = ('' - + str(convert_date['start']['y']) + + str(period['start']['y']) ) video_duration = 360 @@ -3295,12 +3453,72 @@ if __name__ == "__main__": video_scale_y=int(convert_config['video_scale_y']), video_framerate=int(convert_config['video_framerate']), video_duration=video_duration, - temp_path=temp_path + temp_path=temp_path + sep + Do.random_string(8) ) - else: - logging.info(msg='Period was not selected. Exit.') - elif args['publisher']: - logging.info(msg='Starting publish content from templates') + logging.info(msg='Finished convert JPEG collection ' + key) + if args['publisher']: + logging.info(msg='Starting publish content from templates') + + conf = Parse(parameters=args['config'], block='enable-publish') + for key, value in conf.data.items(): + if value == 'true': + publish_config = Parse( + parameters=args['config'], + block='publish-config:' + key + ).data + video_root_path = path.dirname(path.realpath(__file__)) + video_root_user = None + video_root_pass = None + video_find_names = None + if 'video_root_path' in publish_config: + video_root_path = publish_config['video_root_path'] + if 'video_root_user' in publish_config: + video_root_user = publish_config['video_root_user'] + if 'video_root_pass' in publish_config: + video_root_pass = publish_config['video_root_pass'] + + video_find_names = [] + for name in publish_config['video_find_names'].split(','): + video_find_names.append(name.strip()) + + wp_site_name = None + wp_user_name = None + wp_user_pass = None + wp_update_page_id = None + if publish_config['wp_enabled'] == 'true': + if 'wp_site_name' in publish_config: + wp_site_name = publish_config['wp_site_name'] + if 'wp_user_name' in publish_config: + wp_user_name = publish_config['wp_user_name'] + if 'wp_user_pass' in publish_config: + wp_user_pass = publish_config['wp_user_pass'] + if 'wp_update_page_id' in publish_config: + wp_update_page_id = int(publish_config['wp_update_page_id']) + + tg_api_key = None + tg_chat_id = None + if publish_config['tg_enabled'] == 'true': + if 'tg_api_key' in publish_config: + tg_api_key = publish_config['tg_api_key'] + if 'tg_chat_id' in publish_config: + tg_chat_id = publish_config['tg_chat_id'] + + Publish.run( + video_root_path=video_root_path, + video_root_user=video_root_user, + video_root_pass=video_root_pass, + video_find_names=video_find_names, + temp_path=temp_path + sep + Do.random_string(8), + publish_period=period['period'], + publish_amount=amount, + wp_site_name=wp_site_name, + wp_user_name=wp_user_name, + wp_user_pass=wp_user_pass, + wp_update_page_id=wp_update_page_id, + tg_api_key=tg_api_key, + tg_chat_id=tg_chat_id + ) + logging.info(msg='Finished publish content ' + key) else: logging.info(msg='Start arguments was not selected. Exit.') diff --git a/info/images/cctv-scheduler-0.5.png b/info/images/cctv-scheduler-0.5.png new file mode 100755 index 0000000000000000000000000000000000000000..086e11f1b1faff8a6e983f1f8c59940142dfea6e GIT binary patch literal 355962 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
+z&zB`Uy#f_ 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�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 Xb*ZMiWPEsD82qhRvL?A|k)`lG{crx% literal 0 HcmV?d00001