diff --git a/.vscode/launch.json b/.vscode/launch.json index ae932d2..28e25af 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,6 +19,17 @@ ], "console": "integratedTerminal", "justMyCode": true + }, + { + "name": "Python: cctv-scheduler -b", + "type": "python", + "request": "launch", + "program": "${file}", + "args": [ + "-b" + ], + "console": "integratedTerminal", + "justMyCode": true } ] } \ No newline at end of file diff --git a/README.md b/README.md index e53bdd7..21d3a03 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,10 @@ ____ - [`cctv-scheduler.py`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#cctv-scheduler-py) - [`converter.sh`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#converter-sh) - [`publisher.sh`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#publisher-sh) -- [`streaming.py`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#streaming-py) ____ -![cctv-scheduler](info/images/cctv-scheduler-0.2.png) +![cctv-scheduler](info/images/cctv-scheduler-0.3.png) ## `Installation` @@ -37,7 +36,6 @@ wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/conve 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 -wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/streaming.py -O /home/user/cctv-scheduler/streaming.py ``` ### `Configuration` @@ -61,7 +59,7 @@ 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. +> [Hikvision](https://git.hmp.today/pavel.muhortov/cctv-scheduler/src/branch/master/info/hikvision/manual/isapi.pdf) PTZ IP-Camera management. Media streaming. > Additionally: > > - getting temperature from DS18B20 over SSH, @@ -73,10 +71,12 @@ crontab -e > > - [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/)) | 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`| @@ -91,6 +91,7 @@ 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 ``` @@ -196,42 +197,3 @@ Example usage with cron: 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 ``` - -____ - -## `streaming`.py - -**Description:** -> FFmpeg management from Python - -**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/)) -> - [ffmpeg](https://ffmpeg.org/download.html) (tested version 4.3.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) - -| PARAMETERS | DESCRIPTION | DEFAULT| -|-------------|-------------|--------| -|**-s**, **--src**|sources urls|**REQUIRED**| -|**[-h]**|print help and exit|| -|**[--preset]**|240p, 360p, 480p, 720p, 1080p, 1440p, 2160p|`None`| -|**[--fps]**|frame per second encoding output|`None`| -|**[--dst]**|destination url|`None`| -|**[--ffpath]**|alternative path to bin|`None`| -|**[--watchdog]**|detect ffmpeg freeze and terminate|| -|**[--sec]**|seconds to wait before the watchdog terminates|15| -|**[--mono]**|detect ffmpeg running copy and terminate|| - -Example usage in terminal with make the script executable: - -```bash -chmod u+x ./streaming.py -./streaming.py -s rtsp://user:pass@host:554/Streaming/Channels/101 --dst rtp://239.0.0.1:5554 -``` - -Example usage with cron: - -```bash -# crontab -e -* * * * * /usr/bin/python3 /home/user/cctv-scheduler/streaming.py -s rtsp://user:pass@host:554/Streaming/Channels/video,http://Streaming/Channels/audio --dst rtmp://a.rtmp.youtube.com/live2/YOUKEY --mono --watchdog --sec 30 >> /dev/null 2>&1 -* * * * * /usr/bin/python3 /home/user/cctv-scheduler/streaming.py -s ~/media.mp4 --dst rtmp://b.rtmp.youtube.com/live2?backup=1/YOUKEY --mono >> /dev/null 2>&1 -``` diff --git a/archive/0.1/converter.conf b/archive/0.1/converter.conf new file mode 100644 index 0000000..97c039b --- /dev/null +++ b/archive/0.1/converter.conf @@ -0,0 +1,11 @@ +logs=./converter.log + +list=./converter.list +imgroot=/home/user/cctv-scheduler/records +imgnames=point-01 point-02 point-04 point-05 point-11 point-12 +xscale=1920 +yscale=1080 +mp4fps=25 + +telegramapiurl=https://api.telegram.org/bot1234567890:YOURAPIKEY +telegramchatid=123456789 \ No newline at end of file diff --git a/archive/0.1/converter.sh b/archive/0.1/converter.sh new file mode 100644 index 0000000..a231db3 --- /dev/null +++ b/archive/0.1/converter.sh @@ -0,0 +1,208 @@ +#! /bin/bash + +# DESCRIPTION: +# Converting JPEG collection to MP4. +# This is only a local "proof of conept" for testing and debugging. +# +# DEPENDENCIES: +# - ffmpeg +# +# PARAMETERS: +# 1: "qn" - execution without pauses +# 2: custom configuration file path +# 3: periods: '' - today | '-d' - yesterday | '-w' - last week | '-m' - last month | '-y' - last year +# +# FUNCTIONS: +# + +####################################### +# Print message and add to log. +# Globals: +# logs +# Arguments: +# 1: message to print and logging +####################################### +addtologs() { + echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" +} + +####################################### +# Waiting for press [ENTER]. +# Globals: +# None +# Arguments: +# None +####################################### +execpause() { + read -r -p "Press [ENTER] to continue... " +} + +####################################### +# Exit procedure. +# Globals: +# show +# Arguments: +# None +####################################### +execquite() { + addtologs "execution time is $(($(date +%s)-time)) seconds, exit" + if [ "${show}" != "qn" ]; then + execpause + fi + exit +} + +####################################### +# Error exit procedure with Telegram notification. +# Globals: +# telegramapiurl +# telegramchatid +# Arguments: +# 1: message to print and logging +####################################### +execerror() { + addtologs "error: $1" + curl -s -X POST "${telegramapiurl}/sendMessage" \ + -d "chat_id=${telegramchatid}" \ + -d "text=$(basename -s .sh "$0") error: $1" \ + >> /dev/null 2>&1 + execquite +} + +####################################### +# Parsing config file and creating global vars. +# Globals: +# None +# Arguments: +# None +####################################### +getconfig() { + logs=$(grep "logs=" "${conf}" | cut -d= -f2) + list=$(grep "list=" "${conf}" | cut -d= -f2) + imgroot=$(grep "imgroot=" "${conf}" | cut -d= -f2) + IFS=" " read -r -a imgnames <<< "$(grep "imgnames=" "${conf}" | cut -d= -f2)" + xscale=$(grep "xscale=" "${conf}" | cut -d= -f2) + yscale=$(grep "yscale=" "${conf}" | cut -d= -f2) + mp4fps=$(grep "mp4fps=" "${conf}" | cut -d= -f2) + telegramapiurl=$(grep "telegramapiurl=" "${conf}" | cut -d= -f2) + telegramchatid=$(grep "telegramchatid=" "${conf}" | cut -d= -f2) +} + +# +# VARIABLES: +# + +show=$1 +conf=$2 +if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then + conf="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").conf" +fi +when=$3 +if [ -z "${when}" ]; then + d=$(date +"%d") + w=$(date +"%V") + m=$(date +"%m") + y=$(date +"%Y") + duration=1 + imgpath="${y}/${m}/${w}/${d}" + imgname="${y}.${m}.${d}" +fi +if [ "${when}" == "-d" ]; then + d=$(date -d "-1 day" +"%d") + m=$(date +"%m") + if [ "$(date -d '-1 day' +'%m')" != "$(date +'%m')" ]; then + m=$(date -d '-1 day' +'%m') + fi + y=$(date +"%Y") + if [ "$(date -d '-1 day' +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d '-1 day' +'%Y') + fi + w=$(date +"%V") + if [ "$(date +'%w')" == "1" ]; then + w=$(date -d "-1 week" +"%V") + fi + duration=1 + imgpath="${y}/${m}/${w}/${d}" + imgname="${y}.${m}.${d}" +fi +if [ "${when}" == "-w" ]; then + w=$(date -d "-1 week" +"%V") + m=$(date +"%m") + if [ "$(date -d '-1 week' +'%m')" != "$(date +'%m')" ]; then + m=$(date -d '-1 week' +'%m') + fi + y=$(date +"%Y") + if [ "$(date -d '-1 week' +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d '-1 week' +'%Y') + fi + duration=7 + imgpath="${y}/*/${w}" + imgname="${y}-w${w}" +fi +if [ "${when}" == "-m" ]; then + m=$(date -d "-1 month" +"%m") + y=$(date +"%Y") + if [ "$(date -d '-1 month' +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d '-1 month' +'%Y') + fi + duration=30 + imgpath="${y}/${m}" + imgname="${y}.${m}" +fi +if [ "${when}" == "-y" ]; then + y=$(date -d "-1 year" +"%Y") + duration=360 + imgpath="${y}" + imgname="${y}" +fi + +time=$(date +%s) +cd "$(dirname "$(realpath "$0")")" || execerror +if [ ! -e "${conf}" ]; then + execerror "Not found config file: ${conf}" +else + getconfig +fi +if [ -z "${logs}" ];then + logs=/dev/null +elif [ ! -e "${logs}" ];then + touch "${logs}" +fi +if [ -z "${list}" ];then + list="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").list" +fi +if [ ! -e "${list}" ];then + touch "${list}" +fi +if ! command -v ffmpeg &> /dev/null; then + execerror "Not found dependencies" +fi + +# +# MAIN: +# + +for name in "${imgnames[@]}"; do + imgmatch="*${name}*.jpeg" + imgarray=() + while read -r FILE; do + imgarray+=("${FILE}") + done < <(find "${imgroot}/${imgpath}" -name "${imgmatch}" | sort) + imgcount=${#imgarray[*]} + + echo '' > "${list}" + for item in "${imgarray[@]}"; do + echo file \'"${item}"\' >> "${list}" + done + + imgdest="${imgroot}/${name}_${imgname}.mp4" + echo "${imgdest}" + if ffmpeg -r "${imgcount}/${duration}" -f concat -safe 0 -i "${list}" \ + -c:v libx264 -vf "scale=${xscale}:${yscale},fps=${mp4fps},format=yuv420p" \ + "${imgdest}" -y; then + addtologs "converted ${imgcount} images to ${imgdest} with duration ${duration}" + else + execerror "converted ${imgcount} images to ${imgdest} with duration ${duration}" + fi +done +execquite diff --git a/archive/0.1/publisher-template-page-1007.xml b/archive/0.1/publisher-template-page-1007.xml new file mode 100644 index 0000000..4b8ab60 --- /dev/null +++ b/archive/0.1/publisher-template-page-1007.xml @@ -0,0 +1,525 @@ + +wp.editPage + + 1 + wpeditpageid + wpxmlrpcuser + wpxmlrpcpass + + dateCreatedwpeditdateis + useridwpedituserid + page_idwpeditpageid + page_statuspublish + description + + + + <!-- wp:heading {"level":3} --><h3>Now</h3><!-- /wp:heading --> + <!-- wp:embed {"url":"youtubelink","type":"video","providerNameSlug":"youtube", + "responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} --> + <figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"> + <div class="wp-block-embed__wrapper"> + youtubelink + </div></figure><!-- /wp:embed --> + + + <!-- wp:heading {"level":3} --><h3>Yesterday</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"yesterday", + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]", + "currentdp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-yesterday uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentdp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentdp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentdp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentdp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentdp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentdp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"daypoint01", + "header":"currentdp01[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__daypoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1036} --><figure class="wp-block-video"> + <video controls loop src="currentdp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint02","id":1, + "header":"currentdp02[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__daypoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1041} --><figure class="wp-block-video"> + <video controls loop src="currentdp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint04","id":2, + "header":"currentdp04[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__daypoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1046} --><figure class="wp-block-video"> + <video controls loop src="currentdp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint05","id":3, + "header":"currentdp05[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__daypoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1051} --><figure class="wp-block-video"> + <video controls loop src="currentdp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint11","id":4, + "header":"currentdp11[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__daypoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1056} --><figure class="wp-block-video"> + <video controls loop src="currentdp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint12","id":5, + "header":"currentdp12[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__daypoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1061} --><figure class="wp-block-video"> + <video controls loop src="currentdp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + <!-- wp:heading {"level":3} --><h3>Last week</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"lastweek", + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]", + "currentwp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-lastweek uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentwp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentwp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentwp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentwp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentwp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentwp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"weekpoint01", + "header":"currentwp01[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__weekpoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1036} --><figure class="wp-block-video"> + <video controls loop src="currentwp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint02","id":1, + "header":"currentwp02[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__weekpoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1041} --><figure class="wp-block-video"> + <video controls loop src="currentwp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint04","id":2, + "header":"currentwp04[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__weekpoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1046} --><figure class="wp-block-video"> + <video controls loop src="currentwp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint05","id":3, + "header":"currentwp05[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__weekpoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1051} --><figure class="wp-block-video"> + <video controls loop src="currentwp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint11","id":4, + "header":"currentwp11[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__weekpoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1056} --><figure class="wp-block-video"> + <video controls loop src="currentwp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint12","id":5, + "header":"currentwp12[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__weekpoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1061} --><figure class="wp-block-video"> + <video controls loop src="currentwp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + <!-- wp:heading {"level":3} --><h3>Last month</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"lastmonth", + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]", + "currentmp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-lastmonth uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentmp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentmp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentmp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentmp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentmp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentmp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"monthpoint01", + "header":"currentmp01[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__monthpoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1037} --><figure class="wp-block-video"> + <video controls loop src="currentmp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint02","id":1, + "header":"currentmp02[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__monthpoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1042} --><figure class="wp-block-video"> + <video controls loop src="currentmp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint04","id":2, + "header":"currentmp04[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__monthpoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1047} --><figure class="wp-block-video"> + <video controls loop src="currentmp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint05","id":3, + "header":"currentmp05[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__monthpoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1052} --><figure class="wp-block-video"> + <video controls loop src="currentmp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint11","id":4, + "header":"currentmp11[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__monthpoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1057} --><figure class="wp-block-video"> + <video controls loop src="currentmp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint12","id":5, + "header":"currentmp12[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__monthpoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1062} --><figure class="wp-block-video"> + <video controls loop src="currentmp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + <!-- wp:heading {"level":3} --><h3>Last year</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"lastyear", + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]", + "currentyp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-lastyear uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentyp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentyp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentyp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentyp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentyp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentyp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"yearpoint01", + "header":"currentyp01[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__yearpoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1038} --><figure class="wp-block-video"> + <video controls loop src="currentyp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint02","id":1, + "header":"currentyp02[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__yearpoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1043} --><figure class="wp-block-video"> + <video controls loop src="currentyp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint04","id":2, + "header":"currentyp04[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__yearpoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1048} --><figure class="wp-block-video"> + <video controls loop src="currentyp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint05","id":3, + "header":"currentyp05[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__yearpoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1053} --><figure class="wp-block-video"> + <video controls loop src="currentyp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint11","id":4, + "header":"currentyp11[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__yearpoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1058} --><figure class="wp-block-video"> + <video controls loop src="currentyp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint12","id":5, + "header":"currentyp12[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__yearpoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1063} --><figure class="wp-block-video"> + <video controls loop src="currentyp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + + + titleMedia + linkwppagelinkis + permaLinkwppagelinkis + categories + excerpt + text_more + mt_allow_comments0 + mt_allow_pings0 + wp_slugmedia + wp_password + wp_authorwppageauthor + wp_page_parent_id0 + wp_page_parent_title + wp_page_order0 + wp_author_idwpedituserid + wp_author_display_namewppageauthor + date_created_gmtwpeditdateis + custom_fields + + id4258 + keycontent_display_featured_image + value0 + + + id4257 + keycontent_display_title + value0 + + + id4259 + keycontent_width + valuecontainer + + + id4253 + keydisable_space_above_footer + value0 + + + id4252 + keydisable_space_below_header + value0 + + + id4251 + keyhide_footer + value0 + + + id4250 + keyhide_header + value0 + + + id4254 + keyhide_titlebar + value0 + + + id4255 + keypage_overwrite_defaults + value0 + + + id4260 + keysidebar_layout + valuedefault-sidebar + + + id4256 + keytitlebar_padding + value80 + + + wp_page_templatedefault + + + \ No newline at end of file diff --git a/archive/0.1/publisher.conf b/archive/0.1/publisher.conf new file mode 100644 index 0000000..e9b7d94 --- /dev/null +++ b/archive/0.1/publisher.conf @@ -0,0 +1,86 @@ +logs=./publisher.log + +pathroot=/home/user/cctv-scheduler/records +vidnamesarray=point-01 point-02 point-04 point-05 point-11 point-12 + + +telegramapiurl=https://api.telegram.org/bot1234567890:YOURAPIKEY +telegramchatid=123456789 +tgpreviewlink=https://www.hmp.today/wp-content/uploads/2021/02/site-slider_hmp-qr_bwg-3840x1705-1.png +tgpreviewtext=https://www.hmp.today/media + + +wpxmlrpclink=https://www.hmp.today/xmlrpc.php +wpxmlrpcuser=user +wpxmlrpcpass=pass +wppageauthor=author +wppagelinkis=https://www.hmp.today/media +wpeditpageid=1007 +wpedituserid=0 +wpeditdateis=20220707T00:00:00 +wptemplateis=./publisher-template-page-1007.xml + + +youtubelink=https://youtu.be/link + + +defaultdp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.dd_.mp4 +defaultwp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_w.mp4 +defaultmp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.mp4 +defaultyp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mp4 + +defaultdp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mm_.dd_.mp4 +defaultwp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_w.mp4 +defaultmp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mm_.mp4 +defaultyp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mp4 + +defaultdp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mm_.dd_.mp4 +defaultwp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_w.mp4 +defaultmp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mm_.mp4 +defaultyp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mp4 + +defaultdp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mm_.dd_.mp4 +defaultwp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_w.mp4 +defaultmp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mm_.mp4 +defaultyp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mp4 + +defaultdp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mm_.dd_.mp4 +defaultwp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_w.mp4 +defaultmp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mm_.mp4 +defaultyp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mp4 + +defaultdp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.dd_.mp4 +defaultwp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_w.mp4 +defaultmp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.mp4 +defaultyp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mp4 + + +currentdp01=point-01 +currentwp01=point-01 +currentmp01=point-01 +currentyp01=point-01 + +currentdp02=point-02 +currentwp02=point-02 +currentmp02=point-02 +currentyp02=point-02 + +currentdp04=point-04 +currentwp04=point-04 +currentmp04=point-04 +currentyp04=point-04 + +currentdp05=point-05 +currentwp05=point-05 +currentmp05=point-05 +currentyp05=point-05 + +currentdp11=point-11 +currentwp11=point-11 +currentmp11=point-11 +currentyp11=point-11 + +currentdp12=point-12 +currentwp12=point-12 +currentmp12=point-12 +currentyp12=point-12 diff --git a/archive/0.1/publisher.sh b/archive/0.1/publisher.sh new file mode 100644 index 0000000..205da6d --- /dev/null +++ b/archive/0.1/publisher.sh @@ -0,0 +1,599 @@ +#! /bin/bash + +# DESCRIPTION: +# Uploading MP4 to Wordpress and Telegram. +# Additionally: +# - editing Wordpress page from template +# - recompressing video if size over 50MB +# This is only a local "proof of conept" for testing and debugging. +# +# DEPENDENCIES: +# - curl +# - ffmpeg +# - libxml2-utils +# - jq +# +# PARAMETERS: +# 1: "qn" - execution without pauses +# 2: custom configuration file path +# 3: periods: '' - today | '-d' - yesterday | '-w' - last week | '-m' - last month | '-y' - last year +# 4: period multiplier: '' - 1 day|week|month|year +# 5: publishing '--onlytg' - only to Telegram | '--onlywp' - only to Wordpress +# +# FUNCTIONS: +# + +####################################### +# Print message and add to log. +# Globals: +# logs +# Arguments: +# 1: message to print and logging +####################################### +addtologs() { + echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" +} + +####################################### +# Waiting for press [ENTER]. +# Globals: +# None +# Arguments: +# None +####################################### +execpause() { + read -r -p "Press [ENTER] to continue... " +} + +####################################### +# Exit procedure. +# Globals: +# show +# Arguments: +# None +####################################### +execquite() { + addtologs "execution time is $(($(date +%s)-time)) seconds, exit" + if [ "${show}" != "qn" ]; then + execpause + fi + exit +} + +####################################### +# Error exit procedure with Telegram notification. +# Globals: +# telegramapiurl +# telegramchatid +# Arguments: +# 1: message to print and logging +####################################### +execerror() { + addtologs "error: $1" + curl -s -X POST "${telegramapiurl}/sendMessage" \ + -d "chat_id=${telegramchatid}" \ + -d "text=$(basename -s .sh "$0") error: $1" \ + >> /dev/null 2>&1 + execquite +} + +####################################### +# Parsing config file and creating global vars. +# Globals: +# None +# Arguments: +# None +####################################### +getconfig() { + logs=$(grep "logs=" "${conf}" | cut -d= -f2) + pathroot=$(grep "pathroot=" "${conf}" | cut -d= -f2) + IFS=" " read -r -a vidnamesarray <<< "$(grep "vidnamesarray=" "${conf}" | cut -d= -f2)" + telegramapiurl=$(grep "telegramapiurl=" "${conf}" | cut -d= -f2) + telegramchatid=$(grep "telegramchatid=" "${conf}" | cut -d= -f2) + tgpreviewlink=$(grep "tgpreviewlink=" "${conf}" | cut -d= -f2) + tgpreviewtext=$(grep "tgpreviewtext=" "${conf}" | cut -d= -f2) + wpxmlrpclink=$(grep "wpxmlrpclink=" "${conf}" | cut -d= -f2) + wpxmlrpcuser=$(grep "wpxmlrpcuser=" "${conf}" | cut -d= -f2) + wpxmlrpcpass=$(grep "wpxmlrpcpass=" "${conf}" | cut -d= -f2) + wppageauthor=$(grep "wppageauthor=" "${conf}" | cut -d= -f2) + wppagelinkis=$(grep "wppagelinkis=" "${conf}" | cut -d= -f2) + wpeditpageid=$(grep "wpeditpageid=" "${conf}" | cut -d= -f2) + wpedituserid=$(grep "wpedituserid=" "${conf}" | cut -d= -f2) + wpeditdateis=$(grep "wpeditdateis=" "${conf}" | cut -d= -f2) + wptemplateis=$(grep "wptemplateis=" "${conf}" | cut -d= -f2) + youtubelink=$(grep "youtubelink=" "${conf}" | cut -d= -f2) + + IFS=" " read -r -a defaultdp01 <<< "$(grep "defaultdp01=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp01 <<< "$(grep "defaultwp01=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp01 <<< "$(grep "defaultmp01=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp01 <<< "$(grep "defaultyp01=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp02 <<< "$(grep "defaultdp02=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp02 <<< "$(grep "defaultwp02=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp02 <<< "$(grep "defaultmp02=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp02 <<< "$(grep "defaultyp02=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp04 <<< "$(grep "defaultdp04=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp04 <<< "$(grep "defaultwp04=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp04 <<< "$(grep "defaultmp04=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp04 <<< "$(grep "defaultyp04=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp05 <<< "$(grep "defaultdp05=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp05 <<< "$(grep "defaultwp05=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp05 <<< "$(grep "defaultmp05=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp05 <<< "$(grep "defaultyp05=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp11 <<< "$(grep "defaultdp11=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp11 <<< "$(grep "defaultwp11=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp11 <<< "$(grep "defaultmp11=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp11 <<< "$(grep "defaultyp11=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp12 <<< "$(grep "defaultdp12=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp12 <<< "$(grep "defaultwp12=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp12 <<< "$(grep "defaultmp12=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp12 <<< "$(grep "defaultyp12=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a currentdp01 <<< "$(grep "currentdp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp01[1]}" ]; then + currentdp01=${defaultdp01[*]} + fi + IFS=" " read -r -a currentwp01 <<< "$(grep "currentwp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp01[1]}" ]; then + currentwp01=${defaultwp01[*]} + fi + IFS=" " read -r -a currentmp01 <<< "$(grep "currentmp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp01[1]}" ]; then + currentmp01=${defaultmp01[*]} + fi + IFS=" " read -r -a currentyp01 <<< "$(grep "currentyp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp01[1]}" ]; then + currentyp01=${defaultyp01[*]} + fi + + IFS=" " read -r -a currentdp02 <<< "$(grep "currentdp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp02[1]}" ]; then + currentdp02=${defaultdp02[*]} + fi + IFS=" " read -r -a currentwp02 <<< "$(grep "currentwp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp02[1]}" ]; then + currentwp02=${defaultwp02[*]} + fi + IFS=" " read -r -a currentmp02 <<< "$(grep "currentmp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp02[1]}" ]; then + currentmp02=${defaultmp02[*]} + fi + IFS=" " read -r -a currentyp02 <<< "$(grep "currentyp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp02[1]}" ]; then + currentyp02=${defaultyp02[*]} + fi + + IFS=" " read -r -a currentdp04 <<< "$(grep "currentdp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp04[1]}" ]; then + currentdp04=${defaultdp04[*]} + fi + IFS=" " read -r -a currentwp04 <<< "$(grep "currentwp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp04[1]}" ]; then + currentwp04=${defaultwp04[*]} + fi + IFS=" " read -r -a currentmp04 <<< "$(grep "currentmp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp04[1]}" ]; then + currentmp04=${defaultmp04[*]} + fi + IFS=" " read -r -a currentyp04 <<< "$(grep "currentyp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp04[1]}" ]; then + currentyp04=${defaultyp04[*]} + fi + + IFS=" " read -r -a currentdp05 <<< "$(grep "currentdp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp05[1]}" ]; then + currentdp05=${defaultdp05[*]} + fi + IFS=" " read -r -a currentwp05 <<< "$(grep "currentwp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp05[1]}" ]; then + currentwp05=${defaultwp05[*]} + fi + IFS=" " read -r -a currentmp05 <<< "$(grep "currentmp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp05[1]}" ]; then + currentmp05=${defaultmp05[*]} + fi + IFS=" " read -r -a currentyp05 <<< "$(grep "currentyp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp05[1]}" ]; then + currentyp05=${defaultyp05[*]} + fi + + IFS=" " read -r -a currentdp11 <<< "$(grep "currentdp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp11[1]}" ]; then + currentdp11=${defaultdp11[*]} + fi + IFS=" " read -r -a currentwp11 <<< "$(grep "currentwp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp11[1]}" ]; then + currentwp11=${defaultwp11[*]} + fi + IFS=" " read -r -a currentmp11 <<< "$(grep "currentmp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp11[1]}" ]; then + currentmp11=${defaultmp11[*]} + fi + IFS=" " read -r -a currentyp11 <<< "$(grep "currentyp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp11[1]}" ]; then + currentyp11=${defaultyp11[*]} + fi + + IFS=" " read -r -a currentdp12 <<< "$(grep "currentdp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp12[1]}" ]; then + currentdp12=${defaultdp12[*]} + fi + IFS=" " read -r -a currentwp12 <<< "$(grep "currentwp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp12[1]}" ]; then + currentwp12=${defaultwp12[*]} + fi + IFS=" " read -r -a currentmp12 <<< "$(grep "currentmp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp12[1]}" ]; then + currentmp12=${defaultmp12[*]} + fi + IFS=" " read -r -a currentyp12 <<< "$(grep "currentyp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp12[1]}" ]; then + currentyp12=${defaultyp12[*]} + fi +} + +####################################### +# Writing changes to configuration file. +# Globals: +# conf +# when +# vidnamesarray +# vidlinksarray +# Arguments: +# None +####################################### +setconfig() { + if [ -z "${when}" ] || [ "${when}" == "-d" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentdp01=' "${conf}")#currentdp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentdp02=' "${conf}")#currentdp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentdp04=' "${conf}")#currentdp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentdp05=' "${conf}")#currentdp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentdp11=' "${conf}")#currentdp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentdp12=' "${conf}")#currentdp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi + if [ "${when}" == "-w" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentwp01=' "${conf}")#currentwp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentwp02=' "${conf}")#currentwp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentwp04=' "${conf}")#currentwp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentwp05=' "${conf}")#currentwp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentwp11=' "${conf}")#currentwp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentwp12=' "${conf}")#currentwp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi + if [ "${when}" == "-m" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentmp01=' "${conf}")#currentmp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentmp02=' "${conf}")#currentmp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentmp04=' "${conf}")#currentmp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentmp05=' "${conf}")#currentmp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentmp11=' "${conf}")#currentmp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentmp12=' "${conf}")#currentmp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi + if [ "${when}" == "-y" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentyp01=' "${conf}")#currentyp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentyp02=' "${conf}")#currentyp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentyp04=' "${conf}")#currentyp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentyp05=' "${conf}")#currentyp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentyp11=' "${conf}")#currentyp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentyp12=' "${conf}")#currentyp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi +} + +# +# VARIABLES: +# + +show=$1 +conf=$2 +if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then + conf="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").conf" +fi +ever=$4 +if [ -z "${ever}" ] || [ "${ever}" == "-" ]; then + ever=1 +fi +if grep -q -o "^[0-9][0-9]*$" <<< "${ever}"; then + : +else + execerror "${ever} - wrong argument" +fi +when=$3 +if [ -z "${when}" ]; then + viddate=$(date +"%Y").$(date +"%m").$(date +"%d") +fi +if [ "${when}" == "-d" ]; then + d=$(date -d "-${ever} day" +"%d") + m=$(date +"%m") + if [ "$(date -d "-${ever} day" +'%m')" != "$(date +'%m')" ]; then + m=$(date -d "-${ever} day" +'%m') + fi + y=$(date +"%Y") + if [ "$(date -d "-${ever} day" +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d "-${ever} day" +'%Y') + fi + viddate="${y}.${m}.${d}" +fi +if [ "${when}" == "-w" ]; then + w=$(date -d "-${ever} week" +"%V") + y=$(date +"%Y") + if [ "$(date -d "-${ever} week" +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d "-${ever} week" +'%Y') + fi + viddate="${y}-w${w}" +fi +if [ "${when}" == "-m" ]; then + m=$(date -d "-${ever} month" +"%m") + y=$(date +"%Y") + if [ "$(date -d "-${ever} month" +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d "-${ever} month" +'%Y') + fi + viddate="${y}.${m}" +fi +if [ "${when}" == "-y" ]; then + y=$(date -d "-${ever} year" +"%Y") + viddate="${y}" +fi +vidname="${viddate}.mp4" +only=$5 + +time=$(date +%s) +cd "$(dirname "$(realpath "$0")")" || execerror +if [ ! -e "${conf}" ]; then + execerror "Not found config file: ${conf}" +else + getconfig +fi +if [ -z "${logs}" ]; then + logs=/dev/null +elif [ ! -e "${logs}" ]; then + touch "${logs}" +fi +if ! command -v curl &> /dev/null || \ + ! command -v ffmpeg &> /dev/null || \ + ! command -v xmllint &> /dev/null || \ + ! command -v jq &> /dev/null; then + execerror "Not found dependencies" +fi + +# +# MAIN: +# + +vidpathsarray=() +vidlinksarray=() +vidtgidsarray=() +vidtgcpsarray=() +for name in "${vidnamesarray[@]}"; do + vidmatch="${name}_${vidname}" + while read -r FILE; do + vidpathsarray+=("${FILE}") + done < <(find "${pathroot}" -name "*${vidmatch}" | sort) +done +for item in "${!vidpathsarray[@]}"; do + # WORDPRESS UPLOAD VIDEO + if [ ! "$only" == "--onlytg" ]; then + # This realisation isn't optimal, but it fixes a few issues with large files: + # variable=$(base64 $file) -> "xrealloc: cannot allocate" + # response=$(curl -X POST -d @${file}.xml $url) -> "curl: option -d: out of memory" + filetype="video/mp4" + echo ' + wp.uploadFile + + 1 + '"${wpxmlrpcuser}"' + '"${wpxmlrpcpass}"' + + name'"$(basename "${vidpathsarray[$item]}")"' + type'"${filetype}"' + bits' > "${vidpathsarray[$item]}.xml" + base64 --wrap=0 "${vidpathsarray[$item]}" >> "${vidpathsarray[$item]}.xml" + echo ' + + + ' >> "${vidpathsarray[$item]}.xml" + response="${vidpathsarray[$item]}-response.xml" + if curl -X POST -T "${vidpathsarray[$item]}.xml" "${wpxmlrpclink}" > "${response}"; then + vidlinksarray+=("$(xmllint --xpath '//member[contains(name,"link")]/value/string/text()' "${response}")") + vidlinkcodeis=$(curl --output /dev/null --silent --write-out "%{http_code}" "${vidlinksarray[$item]}") + else + execerror "${response}" + fi + if [[ ${vidlinkcodeis} -eq 200 ]]; then + rm "${vidpathsarray[$item]}.xml" + rm "${response}" + echo "$(date +'%Y.%m.%d-%H:%M:%S') sent ${vidpathsarray[$item]} to ${vidlinksarray[$item]}" | tee -a "${logs}" + echo -e "WORDPRESS UPLOAD VIDEO RESULT:" + echo -e "item: ${item}" + echo -e "type: ${filetype}" + echo -e "name: ${vidnamesarray[$item]}" + echo -e "base: $(basename "${vidpathsarray[$item]}")" + echo -e "path: ${vidpathsarray[$item]}" + echo -e "link: ${vidlinksarray[$item]}" + echo -e "code: ${vidlinkcodeis}" + else + vidlinksarray[item]='' + fi + fi + + # TELEGRAM SEND/UPLOAD VIDEO + if [ ! "${only}" == "--onlywp" ]; then + videofullpath=${vidpathsarray[$item]} + vidcompressed=${videofullpath//".mp4"/"-compressed.mp4"} + if [ -n "$(find "${videofullpath}" -prune -size +51380224c)" ]; then + duration=$(ffprobe -i "${videofullpath}" \ + -show_entries format=duration -v quiet -of csv="p=0" | cut -d'.' -f 1) + if ffmpeg -i "${videofullpath}" \ + -c:v libx264 -b:v "$((49*8*1000/duration))k" \ + -vf "scale=960:540,fps=25,format=yuv420p" \ + -preset veryslow "${vidcompressed}" -y -loglevel quiet -stats; then + videofullpath=${vidcompressed} + else + execerror "ffmpeg convert ${videofullpath} to ${vidcompressed}" + fi + fi + videobasename=$(basename "${videofullpath}") + videobasename=${videobasename//".mp4"/""} + response=$(curl -s \ + -F "chat_id=${telegramchatid}" \ + -F "video=@${videofullpath}" \ + -F "caption=${videobasename}" \ + "${telegramapiurl}/sendVideo") + if curl -s -F "chat_id=${telegramchatid}" \ + -F "message_id=$(echo "${response}" | jq -r '.result.message_id')" \ + "${telegramapiurl}/deleteMessage"; then + vidtgidsarray+=("$(echo "${response}" | jq -r '.result.video.file_id')") + vidtgcpsarray+=("$(echo "${response}" | jq -r '.result.caption')") + addtologs "sent ${videofullpath} to ${vidtgidsarray[$item]} Telegram file ID" + else + execerror "sent ${videofullpath} to ${telegramchatid} Telegram Chat ID" + fi + if [ -e "${vidcompressed}" ]; then + rm "${vidcompressed}" + fi + fi +done + +# TELEGRAM SEND MEDIAGROUP +if [ ! "${only}" == "--onlywp" ]; then + response=$(curl -s -F "chat_id=${telegramchatid}" \ + -F media='[ + {"type":"photo","media":"'"${tgpreviewlink}"'", + "caption":"period: '"${viddate}"'\nsource: '"${tgpreviewtext}"'\nstream: '"${youtubelink}"'"}, + {"type":"video","media":"'"${vidtgidsarray[0]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[1]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[2]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[3]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[4]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[5]}"'"}]' \ + -H "Content-Type:multipart/form-data" \ + "${telegramapiurl}/sendMediaGroup") +fi + +# WORDPRESS UPDATE PAGE +if [ ! "${only}" == "--onlytg" ]; then + setconfig + getconfig + xml=$(cat "./${wptemplateis}") + + xml=${xml//wpeditpageid/${wpeditpageid}} + xml=${xml//wpxmlrpcuser/${wpxmlrpcuser}} + xml=${xml//wpxmlrpcpass/${wpxmlrpcpass}} + + xml=${xml//youtubelink/${youtubelink}} + + xml=${xml//"currentdp01[0]"/${currentdp01[0]}} + xml=${xml//"currentdp01[1]"/${currentdp01[1]}} + xml=${xml//"currentdp02[0]"/${currentdp02[0]}} + xml=${xml//"currentdp02[1]"/${currentdp02[1]}} + xml=${xml//"currentdp04[0]"/${currentdp04[0]}} + xml=${xml//"currentdp04[1]"/${currentdp04[1]}} + xml=${xml//"currentdp05[0]"/${currentdp05[0]}} + xml=${xml//"currentdp05[1]"/${currentdp05[1]}} + xml=${xml//"currentdp11[0]"/${currentdp11[0]}} + xml=${xml//"currentdp11[1]"/${currentdp11[1]}} + xml=${xml//"currentdp12[0]"/${currentdp12[0]}} + xml=${xml//"currentdp12[1]"/${currentdp12[1]}} + + xml=${xml//"currentwp01[0]"/${currentwp01[0]}} + xml=${xml//"currentwp01[1]"/${currentwp01[1]}} + xml=${xml//"currentwp02[0]"/${currentwp02[0]}} + xml=${xml//"currentwp02[1]"/${currentwp02[1]}} + xml=${xml//"currentwp04[0]"/${currentwp04[0]}} + xml=${xml//"currentwp04[1]"/${currentwp04[1]}} + xml=${xml//"currentwp05[0]"/${currentwp05[0]}} + xml=${xml//"currentwp05[1]"/${currentwp05[1]}} + xml=${xml//"currentwp11[0]"/${currentwp11[0]}} + xml=${xml//"currentwp11[1]"/${currentwp11[1]}} + xml=${xml//"currentwp12[0]"/${currentwp12[0]}} + xml=${xml//"currentwp12[1]"/${currentwp12[1]}} + + xml=${xml//"currentmp01[0]"/${currentmp01[0]}} + xml=${xml//"currentmp01[1]"/${currentmp01[1]}} + xml=${xml//"currentmp02[0]"/${currentmp02[0]}} + xml=${xml//"currentmp02[1]"/${currentmp02[1]}} + xml=${xml//"currentmp04[0]"/${currentmp04[0]}} + xml=${xml//"currentmp04[1]"/${currentmp04[1]}} + xml=${xml//"currentmp05[0]"/${currentmp05[0]}} + xml=${xml//"currentmp05[1]"/${currentmp05[1]}} + xml=${xml//"currentmp11[0]"/${currentmp11[0]}} + xml=${xml//"currentmp11[1]"/${currentmp11[1]}} + xml=${xml//"currentmp12[0]"/${currentmp12[0]}} + xml=${xml//"currentmp12[1]"/${currentmp12[1]}} + + xml=${xml//"currentyp01[0]"/${currentyp01[0]}} + xml=${xml//"currentyp01[1]"/${currentyp01[1]}} + xml=${xml//"currentyp02[0]"/${currentyp02[0]}} + xml=${xml//"currentyp02[1]"/${currentyp02[1]}} + xml=${xml//"currentyp04[0]"/${currentyp04[0]}} + xml=${xml//"currentyp04[1]"/${currentyp04[1]}} + xml=${xml//"currentyp05[0]"/${currentyp05[0]}} + xml=${xml//"currentyp05[1]"/${currentyp05[1]}} + xml=${xml//"currentyp11[0]"/${currentyp11[0]}} + xml=${xml//"currentyp11[1]"/${currentyp11[1]}} + xml=${xml//"currentyp12[0]"/${currentyp12[0]}} + xml=${xml//"currentyp12[1]"/${currentyp12[1]}} + + xml=${xml//wpedituserid/${wpedituserid}} + xml=${xml//wppageauthor/${wppageauthor}} + xml=${xml//wppagelinkis/${wppagelinkis}} + xml=${xml//wpeditdateis/${wpeditdateis}} + + response=$(curl -d "${xml}" "${wpxmlrpclink}") + if echo "${response}" | grep -q '1'; then + addtologs "update $(grep 'Link' "${xml}" | cut -d'>' -f 6 | cut -d'<' -f 1))" + else + echo "${response}" | xmllint --format - > "${pathroot}/wp-page${wpeditpageid}-response.xml" 2>/dev/null + execerror "${pathroot}/wp-page${wpeditpageid}-response.xml" + fi +fi +execquite diff --git a/streaming.py b/archive/0.1/streaming.py similarity index 100% rename from streaming.py rename to archive/0.1/streaming.py diff --git a/archive/0.2/README.md b/archive/0.2/README.md new file mode 100644 index 0000000..e53bdd7 --- /dev/null +++ b/archive/0.2/README.md @@ -0,0 +1,237 @@ +# cctv-scheduler + +PTZ IP-Camera management +____ + +- [`cctv-scheduler.py`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#cctv-scheduler-py) +- [`converter.sh`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#converter-sh) +- [`publisher.sh`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#publisher-sh) +- [`streaming.py`](https://git.hmp.today/pavel.muhortov/cctv-scheduler#streaming-py) + +____ + +![cctv-scheduler](info/images/cctv-scheduler-0.2.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/converter.sh -O /home/user/cctv-scheduler/converter.sh +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/converter.conf -O /home/user/cctv-scheduler/converter.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 +wget https://git.hmp.today/pavel.muhortov/cctv-scheduler/raw/branch/master/streaming.py -O /home/user/cctv-scheduler/streaming.py +``` + +### `Configuration` + +Edit configs. + +```bash +nano /home/user/cctv-scheduler/cctv-scheduler.conf +nano /home/user/cctv-scheduler/converter.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. +> Additionally: +> +> - getting temperature from DS18B20 over SSH, +> - saving pictures to FTP. +> +> This is only a local "proof of conept" 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) + +| PARAMETERS | DESCRIPTION | DEFAULT| +|-------------|-------------|--------| +|**[-h]**|print help and exit|| +|**[-s, --sequences]**|run sequences from config file|`None`| +|**[--config]**|custom configuration file path|`./cctv-scheduler.conf`| + +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 +0 * * * * /usr/bin/python3 /home/user/cctv-scheduler/cctv-scheduler.py -s +``` + +____ + +## `converter`.sh + +**Description:** +> JPEG to MP4 converter. +> +> 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/)) +> - [ffmpeg](https://ffmpeg.org/download.html) (tested version 4.3.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) +> - filesystem organization: +> +>```bash +> # filesystem organisation 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 +>``` + +| POSITION | PARAMETERS | DESCRIPTION | DEFAULT | +|-----------|--------------|------------------------|---------------| +| 1 | **[qn]** |execution without pauses|| +| 2 | **[/path/to/conf]** |path to config| `./converter.conf` | +| 3 | **[-d\|-w\|-m\|-y]** |periods: '' - today \| '-d' - yesterday \| '-w' - last week \| '-m' - last month \| '-y' - last year|`''`| + +Example usage in terminal with bash for today's MP4 making: + +```bash +bash ./converter.sh - ./converter.conf +``` + +Example usage with cron: + +```bash +# crontab -e +1 0 * * * bash /home/user/cctv-scheduler/converter.sh qn - -d +7 0 * * 1 bash /home/user/cctv-scheduler/converter.sh qn - -w +30 0 1 * * bash /home/user/cctv-scheduler/converter.sh qn - -m +36 0 1 1 * bash /home/user/cctv-scheduler/converter.sh qn - -y +``` + +____ + +## `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 +``` + +____ + +## `streaming`.py + +**Description:** +> FFmpeg management from Python + +**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/)) +> - [ffmpeg](https://ffmpeg.org/download.html) (tested version 4.3.4 on [Debian GNU/Linux 11](http://ftp.debian.org/debian/dists/bullseye/)) + +| PARAMETERS | DESCRIPTION | DEFAULT| +|-------------|-------------|--------| +|**-s**, **--src**|sources urls|**REQUIRED**| +|**[-h]**|print help and exit|| +|**[--preset]**|240p, 360p, 480p, 720p, 1080p, 1440p, 2160p|`None`| +|**[--fps]**|frame per second encoding output|`None`| +|**[--dst]**|destination url|`None`| +|**[--ffpath]**|alternative path to bin|`None`| +|**[--watchdog]**|detect ffmpeg freeze and terminate|| +|**[--sec]**|seconds to wait before the watchdog terminates|15| +|**[--mono]**|detect ffmpeg running copy and terminate|| + +Example usage in terminal with make the script executable: + +```bash +chmod u+x ./streaming.py +./streaming.py -s rtsp://user:pass@host:554/Streaming/Channels/101 --dst rtp://239.0.0.1:5554 +``` + +Example usage with cron: + +```bash +# crontab -e +* * * * * /usr/bin/python3 /home/user/cctv-scheduler/streaming.py -s rtsp://user:pass@host:554/Streaming/Channels/video,http://Streaming/Channels/audio --dst rtmp://a.rtmp.youtube.com/live2/YOUKEY --mono --watchdog --sec 30 >> /dev/null 2>&1 +* * * * * /usr/bin/python3 /home/user/cctv-scheduler/streaming.py -s ~/media.mp4 --dst rtmp://b.rtmp.youtube.com/live2?backup=1/YOUKEY --mono >> /dev/null 2>&1 +``` diff --git a/archive/0.2/cctv-scheduler.conf b/archive/0.2/cctv-scheduler.conf new file mode 100644 index 0000000..36acf93 --- /dev/null +++ b/archive/0.2/cctv-scheduler.conf @@ -0,0 +1,81 @@ +[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-sensors] +# List the sensor block names. Only blocks with the TRUE value will be used. +sensor.test.local = 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 + + +[enable-sequences] +# List the sequence/camera block names. Only blocks with the TRUE value will be used. +camera.test.local = true + + +[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, 1280, 720, -, -, -, -, 5, , 'name: filename prefix, x|y: camera width|height resolution' +step999 = rebootcamera, -, -, -, -, -, -, 120, , diff --git a/archive/0.2/cctv-scheduler.py b/archive/0.2/cctv-scheduler.py new file mode 100644 index 0000000..e119e78 --- /dev/null +++ b/archive/0.2/cctv-scheduler.py @@ -0,0 +1,1240 @@ +#!/usr/bin/env python3 + + +import logging +import urllib.request +from argparse import ArgumentParser +from datetime import datetime +from ftplib import FTP +from os import path, sep, makedirs, remove, replace +from time import sleep +from paramiko import SSHClient, AutoAddPolicy + + +class Parse: + """Parser of configs, arguments, parameters. + """ + 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) + + 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) 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) 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: + """Set of connection methods (functions) for various protocols. + """ + @staticmethod + def http( + url: str, method: str = 'GET', + username: str = '', password: str = '', authtype: str = None, + contenttype: str = 'text/plain', contentdata: str = '' + ) -> str: + """Handling HTTP request. + + Args: + url (str): request url. + 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, optional): digest|basic authentication type. Defaults to None. + contenttype (str, optional): 'Content-Type' header. Defaults to 'text/plain'. + contentdata (str, optional): content data. Defaults to ''. + + Returns: + str: HTTP response or 'ERROR'. + """ + + # Preparing authorization + if authtype: + pswd = urllib.request.HTTPPasswordMgrWithDefaultRealm() + pswd.add_password(None, url, username, password) + if authtype == 'basic': + auth = urllib.request.HTTPBasicAuthHandler(pswd) + 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=bytes(contentdata.encode('utf-8')), method=method) + request.add_header('Content-Type', contenttype) + + # Response + try: + response = urllib.request.urlopen(request).read() + logging.debug( + msg='' + + '\n' + 'uri: ' + url + + '\n' + 'method: ' + method + + '\n' + 'username: ' + username + + '\n' + 'password: ' + password + + '\n' + 'authtype: ' + authtype + + '\n' + 'content-type: ' + contenttype + + '\n' + 'content-data: ' + contentdata + ) + if response.startswith(b'\xff\xd8'): + return response + else: + return str(response.decode('utf-8')) + except Exception as error: + logging.debug(msg='\n' + 'error: ' + str(error)) + return 'ERROR' + + @staticmethod + 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 + 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 + 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) + except Exception: + pass + + with open(src_file, "rb") as file: + ftp.storbinary(f"STOR {dst_file}", file) + ftp.quit() + return True + ''' + @staticmethod + 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() + except Exception: + pass + ''' + ''' + @staticmethod + def xmlrpc(): + pass + ''' + + +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. + """ + return self.http( + url=url, method=method, + username=self._user, password=self._pswd, authtype=self._auth, + contenttype=contenttype, contentdata=contentdata + ) + + 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): absolute path of picture 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 + ) + + 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 Sequence: + """Sequence handling. + """ + @staticmethod + 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.now().strftime('%Y') + dm = datetime.now().strftime('%m') + dv = datetime.now().strftime('%V') + dd = datetime.now().strftime('%d') + th = datetime.now().strftime('%H') + tm = datetime.now().strftime('%M') + ts = 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') + + +if __name__ == "__main__": + time_start = 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('-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 = vars(args.parse_args()) + + log_root = path.dirname(path.realpath(__file__)) + log_level = 'INFO' + 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 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']: + logging.info(msg='Starting convert JPEG collection to MP4') + elif args['publisher']: + logging.info(msg='Starting publish content from templates') + else: + logging.info(msg='Start arguments was not selected. Exit.') + + time_execute = datetime.now() - time_start + logging.info(msg='execution time is ' + str(time_execute) + '. Exit.') diff --git a/archive/0.2/converter.conf b/archive/0.2/converter.conf new file mode 100644 index 0000000..97c039b --- /dev/null +++ b/archive/0.2/converter.conf @@ -0,0 +1,11 @@ +logs=./converter.log + +list=./converter.list +imgroot=/home/user/cctv-scheduler/records +imgnames=point-01 point-02 point-04 point-05 point-11 point-12 +xscale=1920 +yscale=1080 +mp4fps=25 + +telegramapiurl=https://api.telegram.org/bot1234567890:YOURAPIKEY +telegramchatid=123456789 \ No newline at end of file diff --git a/archive/0.2/converter.sh b/archive/0.2/converter.sh new file mode 100644 index 0000000..a231db3 --- /dev/null +++ b/archive/0.2/converter.sh @@ -0,0 +1,208 @@ +#! /bin/bash + +# DESCRIPTION: +# Converting JPEG collection to MP4. +# This is only a local "proof of conept" for testing and debugging. +# +# DEPENDENCIES: +# - ffmpeg +# +# PARAMETERS: +# 1: "qn" - execution without pauses +# 2: custom configuration file path +# 3: periods: '' - today | '-d' - yesterday | '-w' - last week | '-m' - last month | '-y' - last year +# +# FUNCTIONS: +# + +####################################### +# Print message and add to log. +# Globals: +# logs +# Arguments: +# 1: message to print and logging +####################################### +addtologs() { + echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" +} + +####################################### +# Waiting for press [ENTER]. +# Globals: +# None +# Arguments: +# None +####################################### +execpause() { + read -r -p "Press [ENTER] to continue... " +} + +####################################### +# Exit procedure. +# Globals: +# show +# Arguments: +# None +####################################### +execquite() { + addtologs "execution time is $(($(date +%s)-time)) seconds, exit" + if [ "${show}" != "qn" ]; then + execpause + fi + exit +} + +####################################### +# Error exit procedure with Telegram notification. +# Globals: +# telegramapiurl +# telegramchatid +# Arguments: +# 1: message to print and logging +####################################### +execerror() { + addtologs "error: $1" + curl -s -X POST "${telegramapiurl}/sendMessage" \ + -d "chat_id=${telegramchatid}" \ + -d "text=$(basename -s .sh "$0") error: $1" \ + >> /dev/null 2>&1 + execquite +} + +####################################### +# Parsing config file and creating global vars. +# Globals: +# None +# Arguments: +# None +####################################### +getconfig() { + logs=$(grep "logs=" "${conf}" | cut -d= -f2) + list=$(grep "list=" "${conf}" | cut -d= -f2) + imgroot=$(grep "imgroot=" "${conf}" | cut -d= -f2) + IFS=" " read -r -a imgnames <<< "$(grep "imgnames=" "${conf}" | cut -d= -f2)" + xscale=$(grep "xscale=" "${conf}" | cut -d= -f2) + yscale=$(grep "yscale=" "${conf}" | cut -d= -f2) + mp4fps=$(grep "mp4fps=" "${conf}" | cut -d= -f2) + telegramapiurl=$(grep "telegramapiurl=" "${conf}" | cut -d= -f2) + telegramchatid=$(grep "telegramchatid=" "${conf}" | cut -d= -f2) +} + +# +# VARIABLES: +# + +show=$1 +conf=$2 +if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then + conf="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").conf" +fi +when=$3 +if [ -z "${when}" ]; then + d=$(date +"%d") + w=$(date +"%V") + m=$(date +"%m") + y=$(date +"%Y") + duration=1 + imgpath="${y}/${m}/${w}/${d}" + imgname="${y}.${m}.${d}" +fi +if [ "${when}" == "-d" ]; then + d=$(date -d "-1 day" +"%d") + m=$(date +"%m") + if [ "$(date -d '-1 day' +'%m')" != "$(date +'%m')" ]; then + m=$(date -d '-1 day' +'%m') + fi + y=$(date +"%Y") + if [ "$(date -d '-1 day' +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d '-1 day' +'%Y') + fi + w=$(date +"%V") + if [ "$(date +'%w')" == "1" ]; then + w=$(date -d "-1 week" +"%V") + fi + duration=1 + imgpath="${y}/${m}/${w}/${d}" + imgname="${y}.${m}.${d}" +fi +if [ "${when}" == "-w" ]; then + w=$(date -d "-1 week" +"%V") + m=$(date +"%m") + if [ "$(date -d '-1 week' +'%m')" != "$(date +'%m')" ]; then + m=$(date -d '-1 week' +'%m') + fi + y=$(date +"%Y") + if [ "$(date -d '-1 week' +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d '-1 week' +'%Y') + fi + duration=7 + imgpath="${y}/*/${w}" + imgname="${y}-w${w}" +fi +if [ "${when}" == "-m" ]; then + m=$(date -d "-1 month" +"%m") + y=$(date +"%Y") + if [ "$(date -d '-1 month' +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d '-1 month' +'%Y') + fi + duration=30 + imgpath="${y}/${m}" + imgname="${y}.${m}" +fi +if [ "${when}" == "-y" ]; then + y=$(date -d "-1 year" +"%Y") + duration=360 + imgpath="${y}" + imgname="${y}" +fi + +time=$(date +%s) +cd "$(dirname "$(realpath "$0")")" || execerror +if [ ! -e "${conf}" ]; then + execerror "Not found config file: ${conf}" +else + getconfig +fi +if [ -z "${logs}" ];then + logs=/dev/null +elif [ ! -e "${logs}" ];then + touch "${logs}" +fi +if [ -z "${list}" ];then + list="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").list" +fi +if [ ! -e "${list}" ];then + touch "${list}" +fi +if ! command -v ffmpeg &> /dev/null; then + execerror "Not found dependencies" +fi + +# +# MAIN: +# + +for name in "${imgnames[@]}"; do + imgmatch="*${name}*.jpeg" + imgarray=() + while read -r FILE; do + imgarray+=("${FILE}") + done < <(find "${imgroot}/${imgpath}" -name "${imgmatch}" | sort) + imgcount=${#imgarray[*]} + + echo '' > "${list}" + for item in "${imgarray[@]}"; do + echo file \'"${item}"\' >> "${list}" + done + + imgdest="${imgroot}/${name}_${imgname}.mp4" + echo "${imgdest}" + if ffmpeg -r "${imgcount}/${duration}" -f concat -safe 0 -i "${list}" \ + -c:v libx264 -vf "scale=${xscale}:${yscale},fps=${mp4fps},format=yuv420p" \ + "${imgdest}" -y; then + addtologs "converted ${imgcount} images to ${imgdest} with duration ${duration}" + else + execerror "converted ${imgcount} images to ${imgdest} with duration ${duration}" + fi +done +execquite diff --git a/archive/0.2/publisher-template-page-1007.xml b/archive/0.2/publisher-template-page-1007.xml new file mode 100644 index 0000000..4b8ab60 --- /dev/null +++ b/archive/0.2/publisher-template-page-1007.xml @@ -0,0 +1,525 @@ + +wp.editPage + + 1 + wpeditpageid + wpxmlrpcuser + wpxmlrpcpass + + dateCreatedwpeditdateis + useridwpedituserid + page_idwpeditpageid + page_statuspublish + description + + + + <!-- wp:heading {"level":3} --><h3>Now</h3><!-- /wp:heading --> + <!-- wp:embed {"url":"youtubelink","type":"video","providerNameSlug":"youtube", + "responsive":true,"className":"wp-embed-aspect-4-3 wp-has-aspect-ratio"} --> + <figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"> + <div class="wp-block-embed__wrapper"> + youtubelink + </div></figure><!-- /wp:embed --> + + + <!-- wp:heading {"level":3} --><h3>Yesterday</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"yesterday", + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]", + "currentdp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-yesterday uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentdp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentdp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentdp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentdp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentdp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentdp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"daypoint01", + "header":"currentdp01[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__daypoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1036} --><figure class="wp-block-video"> + <video controls loop src="currentdp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint02","id":1, + "header":"currentdp02[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__daypoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1041} --><figure class="wp-block-video"> + <video controls loop src="currentdp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint04","id":2, + "header":"currentdp04[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__daypoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1046} --><figure class="wp-block-video"> + <video controls loop src="currentdp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint05","id":3, + "header":"currentdp05[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__daypoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1051} --><figure class="wp-block-video"> + <video controls loop src="currentdp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint11","id":4, + "header":"currentdp11[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__daypoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1056} --><figure class="wp-block-video"> + <video controls loop src="currentdp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"daypoint12","id":5, + "header":"currentdp12[0]","tabActive":5, + "tabHeaders":["currentdp01[0]", + "currentdp02[0]", + "currentdp04[0]", + "currentdp05[0]", + "currentdp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__daypoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1061} --><figure class="wp-block-video"> + <video controls loop src="currentdp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + <!-- wp:heading {"level":3} --><h3>Last week</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"lastweek", + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]", + "currentwp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-lastweek uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentwp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentwp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentwp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentwp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentwp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentwp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"weekpoint01", + "header":"currentwp01[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__weekpoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1036} --><figure class="wp-block-video"> + <video controls loop src="currentwp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint02","id":1, + "header":"currentwp02[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__weekpoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1041} --><figure class="wp-block-video"> + <video controls loop src="currentwp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint04","id":2, + "header":"currentwp04[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__weekpoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1046} --><figure class="wp-block-video"> + <video controls loop src="currentwp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint05","id":3, + "header":"currentwp05[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__weekpoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1051} --><figure class="wp-block-video"> + <video controls loop src="currentwp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint11","id":4, + "header":"currentwp11[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__weekpoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1056} --><figure class="wp-block-video"> + <video controls loop src="currentwp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"weekpoint12","id":5, + "header":"currentwp12[0]","tabActive":5, + "tabHeaders":["currentwp01[0]", + "currentwp02[0]", + "currentwp04[0]", + "currentwp05[0]", + "currentwp11[0]","wp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__weekpoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1061} --><figure class="wp-block-video"> + <video controls loop src="currentwp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + <!-- wp:heading {"level":3} --><h3>Last month</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"lastmonth", + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]", + "currentmp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-lastmonth uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentmp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentmp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentmp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentmp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentmp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentmp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"monthpoint01", + "header":"currentmp01[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__monthpoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1037} --><figure class="wp-block-video"> + <video controls loop src="currentmp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint02","id":1, + "header":"currentmp02[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__monthpoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1042} --><figure class="wp-block-video"> + <video controls loop src="currentmp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint04","id":2, + "header":"currentmp04[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__monthpoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1047} --><figure class="wp-block-video"> + <video controls loop src="currentmp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint05","id":3, + "header":"currentmp05[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__monthpoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1052} --><figure class="wp-block-video"> + <video controls loop src="currentmp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint11","id":4, + "header":"currentmp11[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__monthpoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1057} --><figure class="wp-block-video"> + <video controls loop src="currentmp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"monthpoint12","id":5, + "header":"currentmp12[0]","tabActive":5, + "tabHeaders":["currentmp01[0]", + "currentmp02[0]", + "currentmp04[0]", + "currentmp05[0]", + "currentmp11[0]","mp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__monthpoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1062} --><figure class="wp-block-video"> + <video controls loop src="currentmp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + <!-- wp:heading {"level":3} --><h3>Last year</h3><!-- /wp:heading --> + <!-- wp:uagb/tabs {"block_id":"lastyear", + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]", + "currentyp12[0]"],"tabActive":5,"activeTabBgColor":"#4b4b4b", + "tabTitleLeftMargin":1,"tabTitleRightMargin":1,"tabTitleTopMargin":1,"tabTitleBottomMargin":1} --> + <div class="wp-block-uagb-tabs uagb-block-lastyear uagb-tabs__wrap uagb-tabs__hstyle1-desktop uagb-tabs__vstyle6-tablet uagb-tabs__vstyle6-mobile" data-tab-active="0"> + <ul class="uagb-tabs__panel uagb-tabs__align-left"><li class="uagb-tab uagb-tabs__active"> + <a href="#uagb-tabs__tab0" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="0"> + <span>currentyp01[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab1" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="1"> + <span>currentyp02[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab2" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="2"> + <span>currentyp04[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab3" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="3"> + <span>currentyp05[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab4" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="4"> + <span>currentyp11[0]</span></a></li><li class="uagb-tab "> + <a href="#uagb-tabs__tab5" class="uagb-tabs-list uagb-tabs__icon-position-left" data-tab="5"> + <span>currentyp12[0]</span></a></li></ul> + + <div class="uagb-tabs__body-wrap"> + <!-- wp:uagb/tabs-child {"block_id":"yearpoint01", + "header":"currentyp01[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-0"> + <div class="uagb-blocks__yearpoint01 uagb-tabs__body" aria-labelledby="uagb-tabs__tab0"> + <!-- wp:video {"id":1038} --><figure class="wp-block-video"> + <video controls loop src="currentyp01[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint02","id":1, + "header":"currentyp02[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-1"> + <div class="uagb-blocks__yearpoint02 uagb-tabs__body" aria-labelledby="uagb-tabs__tab1"> + <!-- wp:video {"id":1043} --><figure class="wp-block-video"> + <video controls loop src="currentyp02[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint04","id":2, + "header":"currentyp04[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-2"> + <div class="uagb-blocks__yearpoint04 uagb-tabs__body" aria-labelledby="uagb-tabs__tab2"> + <!-- wp:video {"id":1048} --><figure class="wp-block-video"> + <video controls loop src="currentyp04[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint05","id":3, + "header":"currentyp05[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-3"> + <div class="uagb-blocks__yearpoint05 uagb-tabs__body" aria-labelledby="uagb-tabs__tab3"> + <!-- wp:video {"id":1053} --><figure class="wp-block-video"> + <video controls loop src="currentyp05[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint11","id":4, + "header":"currentyp11[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-4"> + <div class="uagb-blocks__yearpoint11 uagb-tabs__body" aria-labelledby="uagb-tabs__tab4"> + <!-- wp:video {"id":1058} --><figure class="wp-block-video"> + <video controls loop src="currentyp11[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --> + + <!-- wp:uagb/tabs-child {"block_id":"yearpoint12","id":5, + "header":"currentyp12[0]","tabActive":5, + "tabHeaders":["currentyp01[0]", + "currentyp02[0]", + "currentyp04[0]", + "currentyp05[0]", + "currentyp11[0]","yp1tab"]} --> + <div class="wp-block-uagb-tabs-child uagb-tabs__body-container uagb-tabs__inner-tab uagb-inner-tab-5"> + <div class="uagb-blocks__yearpoint12 uagb-tabs__body" aria-labelledby="uagb-tabs__tab5"> + <!-- wp:video {"id":1063} --><figure class="wp-block-video"> + <video controls loop src="currentyp12[1]"> + </video></figure><!-- /wp:video --></div></div><!-- /wp:uagb/tabs-child --></div></div> + <!-- /wp:uagb/tabs --> + + + + + titleMedia + linkwppagelinkis + permaLinkwppagelinkis + categories + excerpt + text_more + mt_allow_comments0 + mt_allow_pings0 + wp_slugmedia + wp_password + wp_authorwppageauthor + wp_page_parent_id0 + wp_page_parent_title + wp_page_order0 + wp_author_idwpedituserid + wp_author_display_namewppageauthor + date_created_gmtwpeditdateis + custom_fields + + id4258 + keycontent_display_featured_image + value0 + + + id4257 + keycontent_display_title + value0 + + + id4259 + keycontent_width + valuecontainer + + + id4253 + keydisable_space_above_footer + value0 + + + id4252 + keydisable_space_below_header + value0 + + + id4251 + keyhide_footer + value0 + + + id4250 + keyhide_header + value0 + + + id4254 + keyhide_titlebar + value0 + + + id4255 + keypage_overwrite_defaults + value0 + + + id4260 + keysidebar_layout + valuedefault-sidebar + + + id4256 + keytitlebar_padding + value80 + + + wp_page_templatedefault + + + \ No newline at end of file diff --git a/archive/0.2/publisher.conf b/archive/0.2/publisher.conf new file mode 100644 index 0000000..e9b7d94 --- /dev/null +++ b/archive/0.2/publisher.conf @@ -0,0 +1,86 @@ +logs=./publisher.log + +pathroot=/home/user/cctv-scheduler/records +vidnamesarray=point-01 point-02 point-04 point-05 point-11 point-12 + + +telegramapiurl=https://api.telegram.org/bot1234567890:YOURAPIKEY +telegramchatid=123456789 +tgpreviewlink=https://www.hmp.today/wp-content/uploads/2021/02/site-slider_hmp-qr_bwg-3840x1705-1.png +tgpreviewtext=https://www.hmp.today/media + + +wpxmlrpclink=https://www.hmp.today/xmlrpc.php +wpxmlrpcuser=user +wpxmlrpcpass=pass +wppageauthor=author +wppagelinkis=https://www.hmp.today/media +wpeditpageid=1007 +wpedituserid=0 +wpeditdateis=20220707T00:00:00 +wptemplateis=./publisher-template-page-1007.xml + + +youtubelink=https://youtu.be/link + + +defaultdp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.dd_.mp4 +defaultwp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_w.mp4 +defaultmp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mm_.mp4 +defaultyp01=point-01 https://www.hmp.today/wp-content/uploads/2022/07/point-01_yyyy.mp4 + +defaultdp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mm_.dd_.mp4 +defaultwp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_w.mp4 +defaultmp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mm_.mp4 +defaultyp02=point-02 https://www.hmp.today/wp-content/uploads/2022/07/point-02_yyyy.mp4 + +defaultdp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mm_.dd_.mp4 +defaultwp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_w.mp4 +defaultmp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mm_.mp4 +defaultyp04=point-04 https://www.hmp.today/wp-content/uploads/2022/07/point-04_yyyy.mp4 + +defaultdp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mm_.dd_.mp4 +defaultwp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_w.mp4 +defaultmp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mm_.mp4 +defaultyp05=point-05 https://www.hmp.today/wp-content/uploads/2022/07/point-05_yyyy.mp4 + +defaultdp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mm_.dd_.mp4 +defaultwp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_w.mp4 +defaultmp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mm_.mp4 +defaultyp11=point-11 https://www.hmp.today/wp-content/uploads/2022/07/point-11_yyyy.mp4 + +defaultdp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.dd_.mp4 +defaultwp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_w.mp4 +defaultmp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mm_.mp4 +defaultyp12=point-12 https://www.hmp.today/wp-content/uploads/2022/07/point-12_yyyy.mp4 + + +currentdp01=point-01 +currentwp01=point-01 +currentmp01=point-01 +currentyp01=point-01 + +currentdp02=point-02 +currentwp02=point-02 +currentmp02=point-02 +currentyp02=point-02 + +currentdp04=point-04 +currentwp04=point-04 +currentmp04=point-04 +currentyp04=point-04 + +currentdp05=point-05 +currentwp05=point-05 +currentmp05=point-05 +currentyp05=point-05 + +currentdp11=point-11 +currentwp11=point-11 +currentmp11=point-11 +currentyp11=point-11 + +currentdp12=point-12 +currentwp12=point-12 +currentmp12=point-12 +currentyp12=point-12 diff --git a/archive/0.2/publisher.sh b/archive/0.2/publisher.sh new file mode 100644 index 0000000..205da6d --- /dev/null +++ b/archive/0.2/publisher.sh @@ -0,0 +1,599 @@ +#! /bin/bash + +# DESCRIPTION: +# Uploading MP4 to Wordpress and Telegram. +# Additionally: +# - editing Wordpress page from template +# - recompressing video if size over 50MB +# This is only a local "proof of conept" for testing and debugging. +# +# DEPENDENCIES: +# - curl +# - ffmpeg +# - libxml2-utils +# - jq +# +# PARAMETERS: +# 1: "qn" - execution without pauses +# 2: custom configuration file path +# 3: periods: '' - today | '-d' - yesterday | '-w' - last week | '-m' - last month | '-y' - last year +# 4: period multiplier: '' - 1 day|week|month|year +# 5: publishing '--onlytg' - only to Telegram | '--onlywp' - only to Wordpress +# +# FUNCTIONS: +# + +####################################### +# Print message and add to log. +# Globals: +# logs +# Arguments: +# 1: message to print and logging +####################################### +addtologs() { + echo "$(date +'%Y.%m.%d-%H:%M:%S') $1" | tee -a "${logs}" +} + +####################################### +# Waiting for press [ENTER]. +# Globals: +# None +# Arguments: +# None +####################################### +execpause() { + read -r -p "Press [ENTER] to continue... " +} + +####################################### +# Exit procedure. +# Globals: +# show +# Arguments: +# None +####################################### +execquite() { + addtologs "execution time is $(($(date +%s)-time)) seconds, exit" + if [ "${show}" != "qn" ]; then + execpause + fi + exit +} + +####################################### +# Error exit procedure with Telegram notification. +# Globals: +# telegramapiurl +# telegramchatid +# Arguments: +# 1: message to print and logging +####################################### +execerror() { + addtologs "error: $1" + curl -s -X POST "${telegramapiurl}/sendMessage" \ + -d "chat_id=${telegramchatid}" \ + -d "text=$(basename -s .sh "$0") error: $1" \ + >> /dev/null 2>&1 + execquite +} + +####################################### +# Parsing config file and creating global vars. +# Globals: +# None +# Arguments: +# None +####################################### +getconfig() { + logs=$(grep "logs=" "${conf}" | cut -d= -f2) + pathroot=$(grep "pathroot=" "${conf}" | cut -d= -f2) + IFS=" " read -r -a vidnamesarray <<< "$(grep "vidnamesarray=" "${conf}" | cut -d= -f2)" + telegramapiurl=$(grep "telegramapiurl=" "${conf}" | cut -d= -f2) + telegramchatid=$(grep "telegramchatid=" "${conf}" | cut -d= -f2) + tgpreviewlink=$(grep "tgpreviewlink=" "${conf}" | cut -d= -f2) + tgpreviewtext=$(grep "tgpreviewtext=" "${conf}" | cut -d= -f2) + wpxmlrpclink=$(grep "wpxmlrpclink=" "${conf}" | cut -d= -f2) + wpxmlrpcuser=$(grep "wpxmlrpcuser=" "${conf}" | cut -d= -f2) + wpxmlrpcpass=$(grep "wpxmlrpcpass=" "${conf}" | cut -d= -f2) + wppageauthor=$(grep "wppageauthor=" "${conf}" | cut -d= -f2) + wppagelinkis=$(grep "wppagelinkis=" "${conf}" | cut -d= -f2) + wpeditpageid=$(grep "wpeditpageid=" "${conf}" | cut -d= -f2) + wpedituserid=$(grep "wpedituserid=" "${conf}" | cut -d= -f2) + wpeditdateis=$(grep "wpeditdateis=" "${conf}" | cut -d= -f2) + wptemplateis=$(grep "wptemplateis=" "${conf}" | cut -d= -f2) + youtubelink=$(grep "youtubelink=" "${conf}" | cut -d= -f2) + + IFS=" " read -r -a defaultdp01 <<< "$(grep "defaultdp01=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp01 <<< "$(grep "defaultwp01=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp01 <<< "$(grep "defaultmp01=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp01 <<< "$(grep "defaultyp01=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp02 <<< "$(grep "defaultdp02=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp02 <<< "$(grep "defaultwp02=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp02 <<< "$(grep "defaultmp02=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp02 <<< "$(grep "defaultyp02=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp04 <<< "$(grep "defaultdp04=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp04 <<< "$(grep "defaultwp04=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp04 <<< "$(grep "defaultmp04=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp04 <<< "$(grep "defaultyp04=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp05 <<< "$(grep "defaultdp05=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp05 <<< "$(grep "defaultwp05=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp05 <<< "$(grep "defaultmp05=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp05 <<< "$(grep "defaultyp05=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp11 <<< "$(grep "defaultdp11=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp11 <<< "$(grep "defaultwp11=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp11 <<< "$(grep "defaultmp11=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp11 <<< "$(grep "defaultyp11=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a defaultdp12 <<< "$(grep "defaultdp12=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultwp12 <<< "$(grep "defaultwp12=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultmp12 <<< "$(grep "defaultmp12=" "${conf}" | cut -d= -f2)" + IFS=" " read -r -a defaultyp12 <<< "$(grep "defaultyp12=" "${conf}" | cut -d= -f2)" + + IFS=" " read -r -a currentdp01 <<< "$(grep "currentdp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp01[1]}" ]; then + currentdp01=${defaultdp01[*]} + fi + IFS=" " read -r -a currentwp01 <<< "$(grep "currentwp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp01[1]}" ]; then + currentwp01=${defaultwp01[*]} + fi + IFS=" " read -r -a currentmp01 <<< "$(grep "currentmp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp01[1]}" ]; then + currentmp01=${defaultmp01[*]} + fi + IFS=" " read -r -a currentyp01 <<< "$(grep "currentyp01=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp01[1]}" ]; then + currentyp01=${defaultyp01[*]} + fi + + IFS=" " read -r -a currentdp02 <<< "$(grep "currentdp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp02[1]}" ]; then + currentdp02=${defaultdp02[*]} + fi + IFS=" " read -r -a currentwp02 <<< "$(grep "currentwp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp02[1]}" ]; then + currentwp02=${defaultwp02[*]} + fi + IFS=" " read -r -a currentmp02 <<< "$(grep "currentmp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp02[1]}" ]; then + currentmp02=${defaultmp02[*]} + fi + IFS=" " read -r -a currentyp02 <<< "$(grep "currentyp02=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp02[1]}" ]; then + currentyp02=${defaultyp02[*]} + fi + + IFS=" " read -r -a currentdp04 <<< "$(grep "currentdp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp04[1]}" ]; then + currentdp04=${defaultdp04[*]} + fi + IFS=" " read -r -a currentwp04 <<< "$(grep "currentwp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp04[1]}" ]; then + currentwp04=${defaultwp04[*]} + fi + IFS=" " read -r -a currentmp04 <<< "$(grep "currentmp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp04[1]}" ]; then + currentmp04=${defaultmp04[*]} + fi + IFS=" " read -r -a currentyp04 <<< "$(grep "currentyp04=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp04[1]}" ]; then + currentyp04=${defaultyp04[*]} + fi + + IFS=" " read -r -a currentdp05 <<< "$(grep "currentdp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp05[1]}" ]; then + currentdp05=${defaultdp05[*]} + fi + IFS=" " read -r -a currentwp05 <<< "$(grep "currentwp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp05[1]}" ]; then + currentwp05=${defaultwp05[*]} + fi + IFS=" " read -r -a currentmp05 <<< "$(grep "currentmp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp05[1]}" ]; then + currentmp05=${defaultmp05[*]} + fi + IFS=" " read -r -a currentyp05 <<< "$(grep "currentyp05=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp05[1]}" ]; then + currentyp05=${defaultyp05[*]} + fi + + IFS=" " read -r -a currentdp11 <<< "$(grep "currentdp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp11[1]}" ]; then + currentdp11=${defaultdp11[*]} + fi + IFS=" " read -r -a currentwp11 <<< "$(grep "currentwp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp11[1]}" ]; then + currentwp11=${defaultwp11[*]} + fi + IFS=" " read -r -a currentmp11 <<< "$(grep "currentmp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp11[1]}" ]; then + currentmp11=${defaultmp11[*]} + fi + IFS=" " read -r -a currentyp11 <<< "$(grep "currentyp11=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp11[1]}" ]; then + currentyp11=${defaultyp11[*]} + fi + + IFS=" " read -r -a currentdp12 <<< "$(grep "currentdp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentdp12[1]}" ]; then + currentdp12=${defaultdp12[*]} + fi + IFS=" " read -r -a currentwp12 <<< "$(grep "currentwp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentwp12[1]}" ]; then + currentwp12=${defaultwp12[*]} + fi + IFS=" " read -r -a currentmp12 <<< "$(grep "currentmp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentmp12[1]}" ]; then + currentmp12=${defaultmp12[*]} + fi + IFS=" " read -r -a currentyp12 <<< "$(grep "currentyp12=" "${conf}" | cut -d= -f2)" + if [ -z "${currentyp12[1]}" ]; then + currentyp12=${defaultyp12[*]} + fi +} + +####################################### +# Writing changes to configuration file. +# Globals: +# conf +# when +# vidnamesarray +# vidlinksarray +# Arguments: +# None +####################################### +setconfig() { + if [ -z "${when}" ] || [ "${when}" == "-d" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentdp01=' "${conf}")#currentdp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentdp02=' "${conf}")#currentdp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentdp04=' "${conf}")#currentdp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentdp05=' "${conf}")#currentdp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentdp11=' "${conf}")#currentdp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentdp12=' "${conf}")#currentdp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi + if [ "${when}" == "-w" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentwp01=' "${conf}")#currentwp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentwp02=' "${conf}")#currentwp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentwp04=' "${conf}")#currentwp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentwp05=' "${conf}")#currentwp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentwp11=' "${conf}")#currentwp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentwp12=' "${conf}")#currentwp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi + if [ "${when}" == "-m" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentmp01=' "${conf}")#currentmp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentmp02=' "${conf}")#currentmp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentmp04=' "${conf}")#currentmp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentmp05=' "${conf}")#currentmp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentmp11=' "${conf}")#currentmp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentmp12=' "${conf}")#currentmp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi + if [ "${when}" == "-y" ]; then + if [ -n "${vidnamesarray[0]}" ] && [ -n "${vidlinksarray[0]}" ]; then + sed -i "s#$(grep 'currentyp01=' "${conf}")#currentyp01=${vidnamesarray[0]} ${vidlinksarray[0]}#" "${conf}" + fi + if [ -n "${vidnamesarray[1]}" ] && [ -n "${vidlinksarray[1]}" ]; then + sed -i "s#$(grep 'currentyp02=' "${conf}")#currentyp02=${vidnamesarray[1]} ${vidlinksarray[1]}#" "${conf}" + fi + if [ -n "${vidnamesarray[2]}" ] && [ -n "${vidlinksarray[2]}" ]; then + sed -i "s#$(grep 'currentyp04=' "${conf}")#currentyp04=${vidnamesarray[2]} ${vidlinksarray[2]}#" "${conf}" + fi + if [ -n "${vidnamesarray[3]}" ] && [ -n "${vidlinksarray[3]}" ]; then + sed -i "s#$(grep 'currentyp05=' "${conf}")#currentyp05=${vidnamesarray[3]} ${vidlinksarray[3]}#" "${conf}" + fi + if [ -n "${vidnamesarray[4]}" ] && [ -n "${vidlinksarray[4]}" ]; then + sed -i "s#$(grep 'currentyp11=' "${conf}")#currentyp11=${vidnamesarray[4]} ${vidlinksarray[4]}#" "${conf}" + fi + if [ -n "${vidnamesarray[5]}" ] && [ -n "${vidlinksarray[5]}" ]; then + sed -i "s#$(grep 'currentyp12=' "${conf}")#currentyp12=${vidnamesarray[5]} ${vidlinksarray[5]}#" "${conf}" + fi + fi +} + +# +# VARIABLES: +# + +show=$1 +conf=$2 +if [ -z "${conf}" ] || [ "${conf}" == "-" ]; then + conf="$(dirname "$(realpath "$0")")/$(basename -s .sh "$0").conf" +fi +ever=$4 +if [ -z "${ever}" ] || [ "${ever}" == "-" ]; then + ever=1 +fi +if grep -q -o "^[0-9][0-9]*$" <<< "${ever}"; then + : +else + execerror "${ever} - wrong argument" +fi +when=$3 +if [ -z "${when}" ]; then + viddate=$(date +"%Y").$(date +"%m").$(date +"%d") +fi +if [ "${when}" == "-d" ]; then + d=$(date -d "-${ever} day" +"%d") + m=$(date +"%m") + if [ "$(date -d "-${ever} day" +'%m')" != "$(date +'%m')" ]; then + m=$(date -d "-${ever} day" +'%m') + fi + y=$(date +"%Y") + if [ "$(date -d "-${ever} day" +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d "-${ever} day" +'%Y') + fi + viddate="${y}.${m}.${d}" +fi +if [ "${when}" == "-w" ]; then + w=$(date -d "-${ever} week" +"%V") + y=$(date +"%Y") + if [ "$(date -d "-${ever} week" +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d "-${ever} week" +'%Y') + fi + viddate="${y}-w${w}" +fi +if [ "${when}" == "-m" ]; then + m=$(date -d "-${ever} month" +"%m") + y=$(date +"%Y") + if [ "$(date -d "-${ever} month" +'%Y')" != "$(date +'%Y')" ]; then + y=$(date -d "-${ever} month" +'%Y') + fi + viddate="${y}.${m}" +fi +if [ "${when}" == "-y" ]; then + y=$(date -d "-${ever} year" +"%Y") + viddate="${y}" +fi +vidname="${viddate}.mp4" +only=$5 + +time=$(date +%s) +cd "$(dirname "$(realpath "$0")")" || execerror +if [ ! -e "${conf}" ]; then + execerror "Not found config file: ${conf}" +else + getconfig +fi +if [ -z "${logs}" ]; then + logs=/dev/null +elif [ ! -e "${logs}" ]; then + touch "${logs}" +fi +if ! command -v curl &> /dev/null || \ + ! command -v ffmpeg &> /dev/null || \ + ! command -v xmllint &> /dev/null || \ + ! command -v jq &> /dev/null; then + execerror "Not found dependencies" +fi + +# +# MAIN: +# + +vidpathsarray=() +vidlinksarray=() +vidtgidsarray=() +vidtgcpsarray=() +for name in "${vidnamesarray[@]}"; do + vidmatch="${name}_${vidname}" + while read -r FILE; do + vidpathsarray+=("${FILE}") + done < <(find "${pathroot}" -name "*${vidmatch}" | sort) +done +for item in "${!vidpathsarray[@]}"; do + # WORDPRESS UPLOAD VIDEO + if [ ! "$only" == "--onlytg" ]; then + # This realisation isn't optimal, but it fixes a few issues with large files: + # variable=$(base64 $file) -> "xrealloc: cannot allocate" + # response=$(curl -X POST -d @${file}.xml $url) -> "curl: option -d: out of memory" + filetype="video/mp4" + echo ' + wp.uploadFile + + 1 + '"${wpxmlrpcuser}"' + '"${wpxmlrpcpass}"' + + name'"$(basename "${vidpathsarray[$item]}")"' + type'"${filetype}"' + bits' > "${vidpathsarray[$item]}.xml" + base64 --wrap=0 "${vidpathsarray[$item]}" >> "${vidpathsarray[$item]}.xml" + echo ' + + + ' >> "${vidpathsarray[$item]}.xml" + response="${vidpathsarray[$item]}-response.xml" + if curl -X POST -T "${vidpathsarray[$item]}.xml" "${wpxmlrpclink}" > "${response}"; then + vidlinksarray+=("$(xmllint --xpath '//member[contains(name,"link")]/value/string/text()' "${response}")") + vidlinkcodeis=$(curl --output /dev/null --silent --write-out "%{http_code}" "${vidlinksarray[$item]}") + else + execerror "${response}" + fi + if [[ ${vidlinkcodeis} -eq 200 ]]; then + rm "${vidpathsarray[$item]}.xml" + rm "${response}" + echo "$(date +'%Y.%m.%d-%H:%M:%S') sent ${vidpathsarray[$item]} to ${vidlinksarray[$item]}" | tee -a "${logs}" + echo -e "WORDPRESS UPLOAD VIDEO RESULT:" + echo -e "item: ${item}" + echo -e "type: ${filetype}" + echo -e "name: ${vidnamesarray[$item]}" + echo -e "base: $(basename "${vidpathsarray[$item]}")" + echo -e "path: ${vidpathsarray[$item]}" + echo -e "link: ${vidlinksarray[$item]}" + echo -e "code: ${vidlinkcodeis}" + else + vidlinksarray[item]='' + fi + fi + + # TELEGRAM SEND/UPLOAD VIDEO + if [ ! "${only}" == "--onlywp" ]; then + videofullpath=${vidpathsarray[$item]} + vidcompressed=${videofullpath//".mp4"/"-compressed.mp4"} + if [ -n "$(find "${videofullpath}" -prune -size +51380224c)" ]; then + duration=$(ffprobe -i "${videofullpath}" \ + -show_entries format=duration -v quiet -of csv="p=0" | cut -d'.' -f 1) + if ffmpeg -i "${videofullpath}" \ + -c:v libx264 -b:v "$((49*8*1000/duration))k" \ + -vf "scale=960:540,fps=25,format=yuv420p" \ + -preset veryslow "${vidcompressed}" -y -loglevel quiet -stats; then + videofullpath=${vidcompressed} + else + execerror "ffmpeg convert ${videofullpath} to ${vidcompressed}" + fi + fi + videobasename=$(basename "${videofullpath}") + videobasename=${videobasename//".mp4"/""} + response=$(curl -s \ + -F "chat_id=${telegramchatid}" \ + -F "video=@${videofullpath}" \ + -F "caption=${videobasename}" \ + "${telegramapiurl}/sendVideo") + if curl -s -F "chat_id=${telegramchatid}" \ + -F "message_id=$(echo "${response}" | jq -r '.result.message_id')" \ + "${telegramapiurl}/deleteMessage"; then + vidtgidsarray+=("$(echo "${response}" | jq -r '.result.video.file_id')") + vidtgcpsarray+=("$(echo "${response}" | jq -r '.result.caption')") + addtologs "sent ${videofullpath} to ${vidtgidsarray[$item]} Telegram file ID" + else + execerror "sent ${videofullpath} to ${telegramchatid} Telegram Chat ID" + fi + if [ -e "${vidcompressed}" ]; then + rm "${vidcompressed}" + fi + fi +done + +# TELEGRAM SEND MEDIAGROUP +if [ ! "${only}" == "--onlywp" ]; then + response=$(curl -s -F "chat_id=${telegramchatid}" \ + -F media='[ + {"type":"photo","media":"'"${tgpreviewlink}"'", + "caption":"period: '"${viddate}"'\nsource: '"${tgpreviewtext}"'\nstream: '"${youtubelink}"'"}, + {"type":"video","media":"'"${vidtgidsarray[0]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[1]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[2]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[3]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[4]}"'"}, + {"type":"video","media":"'"${vidtgidsarray[5]}"'"}]' \ + -H "Content-Type:multipart/form-data" \ + "${telegramapiurl}/sendMediaGroup") +fi + +# WORDPRESS UPDATE PAGE +if [ ! "${only}" == "--onlytg" ]; then + setconfig + getconfig + xml=$(cat "./${wptemplateis}") + + xml=${xml//wpeditpageid/${wpeditpageid}} + xml=${xml//wpxmlrpcuser/${wpxmlrpcuser}} + xml=${xml//wpxmlrpcpass/${wpxmlrpcpass}} + + xml=${xml//youtubelink/${youtubelink}} + + xml=${xml//"currentdp01[0]"/${currentdp01[0]}} + xml=${xml//"currentdp01[1]"/${currentdp01[1]}} + xml=${xml//"currentdp02[0]"/${currentdp02[0]}} + xml=${xml//"currentdp02[1]"/${currentdp02[1]}} + xml=${xml//"currentdp04[0]"/${currentdp04[0]}} + xml=${xml//"currentdp04[1]"/${currentdp04[1]}} + xml=${xml//"currentdp05[0]"/${currentdp05[0]}} + xml=${xml//"currentdp05[1]"/${currentdp05[1]}} + xml=${xml//"currentdp11[0]"/${currentdp11[0]}} + xml=${xml//"currentdp11[1]"/${currentdp11[1]}} + xml=${xml//"currentdp12[0]"/${currentdp12[0]}} + xml=${xml//"currentdp12[1]"/${currentdp12[1]}} + + xml=${xml//"currentwp01[0]"/${currentwp01[0]}} + xml=${xml//"currentwp01[1]"/${currentwp01[1]}} + xml=${xml//"currentwp02[0]"/${currentwp02[0]}} + xml=${xml//"currentwp02[1]"/${currentwp02[1]}} + xml=${xml//"currentwp04[0]"/${currentwp04[0]}} + xml=${xml//"currentwp04[1]"/${currentwp04[1]}} + xml=${xml//"currentwp05[0]"/${currentwp05[0]}} + xml=${xml//"currentwp05[1]"/${currentwp05[1]}} + xml=${xml//"currentwp11[0]"/${currentwp11[0]}} + xml=${xml//"currentwp11[1]"/${currentwp11[1]}} + xml=${xml//"currentwp12[0]"/${currentwp12[0]}} + xml=${xml//"currentwp12[1]"/${currentwp12[1]}} + + xml=${xml//"currentmp01[0]"/${currentmp01[0]}} + xml=${xml//"currentmp01[1]"/${currentmp01[1]}} + xml=${xml//"currentmp02[0]"/${currentmp02[0]}} + xml=${xml//"currentmp02[1]"/${currentmp02[1]}} + xml=${xml//"currentmp04[0]"/${currentmp04[0]}} + xml=${xml//"currentmp04[1]"/${currentmp04[1]}} + xml=${xml//"currentmp05[0]"/${currentmp05[0]}} + xml=${xml//"currentmp05[1]"/${currentmp05[1]}} + xml=${xml//"currentmp11[0]"/${currentmp11[0]}} + xml=${xml//"currentmp11[1]"/${currentmp11[1]}} + xml=${xml//"currentmp12[0]"/${currentmp12[0]}} + xml=${xml//"currentmp12[1]"/${currentmp12[1]}} + + xml=${xml//"currentyp01[0]"/${currentyp01[0]}} + xml=${xml//"currentyp01[1]"/${currentyp01[1]}} + xml=${xml//"currentyp02[0]"/${currentyp02[0]}} + xml=${xml//"currentyp02[1]"/${currentyp02[1]}} + xml=${xml//"currentyp04[0]"/${currentyp04[0]}} + xml=${xml//"currentyp04[1]"/${currentyp04[1]}} + xml=${xml//"currentyp05[0]"/${currentyp05[0]}} + xml=${xml//"currentyp05[1]"/${currentyp05[1]}} + xml=${xml//"currentyp11[0]"/${currentyp11[0]}} + xml=${xml//"currentyp11[1]"/${currentyp11[1]}} + xml=${xml//"currentyp12[0]"/${currentyp12[0]}} + xml=${xml//"currentyp12[1]"/${currentyp12[1]}} + + xml=${xml//wpedituserid/${wpedituserid}} + xml=${xml//wppageauthor/${wppageauthor}} + xml=${xml//wppagelinkis/${wppagelinkis}} + xml=${xml//wpeditdateis/${wpeditdateis}} + + response=$(curl -d "${xml}" "${wpxmlrpclink}") + if echo "${response}" | grep -q '1'; then + addtologs "update $(grep 'Link' "${xml}" | cut -d'>' -f 6 | cut -d'<' -f 1))" + else + echo "${response}" | xmllint --format - > "${pathroot}/wp-page${wpeditpageid}-response.xml" 2>/dev/null + execerror "${pathroot}/wp-page${wpeditpageid}-response.xml" + fi +fi +execquite diff --git a/archive/0.2/streaming.py b/archive/0.2/streaming.py new file mode 100644 index 0000000..b19da1d --- /dev/null +++ b/archive/0.2/streaming.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 + + +from multiprocessing import Process, Queue +from os import path, environ +from subprocess import Popen, PIPE, STDOUT +from sys import platform +from time import sleep + + +class Proc: + """ + Find a running process from Python + """ + @classmethod + def _list_windows(cls) -> list: + """ + Find all running process with wmi + :return: list of 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 + def _list_linux(cls) -> list: + """ + Find all running process with ps + :return: list of 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(cls) -> list: + """ + Find all running process + :return: list of 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 + def search(cls, find: str, exclude: str = None) -> list: + """ + Find specified processes + :param find: find process pid, name or arguments + :param exclude: exclude process pid, name or arguments + :return: list of dictionaries with descriptions of found processes + """ + proc_found = [] + try: + for proc in cls.list(): + 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 + :param pid: process ID + :return: None + """ + 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, preset: str = None, fps: int = None, dst: str = None, + ffpath: str = None, watchdog: bool = False, sec: int = 5, mono: bool = False) -> None: + """ + Running the installed ffmpeg + :param src: sources urls (example: "rtsp://user:pass@host:554/Streaming/Channels/101, anull") + :param preset: 240p, 360p, 480p, 720p, 1080p, 1440p, 2160p + :param fps: frame per second encoding output + :param dst: destination url (example: rtp://239.0.0.1:5554) + :param ffpath: alternative path to bin (example: /usr/bin/ffmpeg) + :param watchdog: detect ffmpeg freeze and terminate + :param sec: seconds to wait before the watchdog terminates + :param mono: detect ffmpeg running copy and terminate + :return: None + """ + process = cls._bin(ffpath).split()+cls._src(src).split()+cls._preset(preset, fps).split()+cls._dst(dst).split() + if mono and Proc.search(' '.join(process)): + print('Process already exist, exit...') + else: + with Popen(process, stdout=PIPE, stderr=STDOUT) as proc: + que = None + if watchdog: + que = Queue() + Process(target=cls._watchdog, args=(proc.pid, sec, que,), daemon=True).start() + for line in proc.stdout: + if not que: + print(line, flush=True) + else: + que.put(line) + exit() + + @classmethod + def _bin(cls, path_ffmpeg: str) -> str: + """ + Returns the path to the ffmpeg depending on the OS + :param path_ffmpeg: alternative path to bin + :return: path to ffmpeg + """ + 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 archive from: 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 archive from: https://evermeet.cx/ffmpeg/\n' + '\tTarget: /usr/bin/ffmpeg\n') + if not path_ffmpeg: + if platform.startswith('linux') or platform.startswith('darwin'): + path_ffmpeg = '/usr/bin/ffmpeg' + elif platform.startswith('win32'): + path_ffmpeg = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" + if path.exists(path_ffmpeg): + return path_ffmpeg + else: + print('ON', platform, 'PLATFORM', 'not found ffmpeg', faq) + return None + + @classmethod + def _src(cls, sources: str) -> list: + """ + Parsing sources into ffmpeg format + :param sources: comma-separated list of sources in string format + :return: 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 + :param choice: preset selection + :param fps: frame per second encoding output + :return: 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 + :param destination: + :return: 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 = 5, que: Queue = None) -> None: + """ + If no data arrives in the queue, kill the process + :param pid: process ID + :param sec: seconds to wait for data + :param que: queue pointer + :return: None + """ + 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() + + +if __name__ == "__main__": + from argparse import ArgumentParser + + args = ArgumentParser( + prog='streaming', + description='FFmpeg management from Python', + epilog='Dependencies: ' + 'Python 3 (tested version 3.9.5 on Debian GNU/Linux 11), ' + 'ffmpeg (tested version 4.3.4 on Debian GNU/Linux 11)' + ) + args.add_argument('-s', '--src', type=str, required=True, + help='sources urls (example: "rtsp://user:pass@host:554/Streaming/Channels/101, anull")') + args.add_argument('--preset', type=str, default=None, required=False, + help='240p, 360p, 480p, 720p, 1080p, 1440p, 2160p') + args.add_argument('--fps', type=int, default=None, required=False, + help='frame per second encoding output') + args.add_argument('--dst', type=str, default=None, required=False, + help='destination url (example: rtp://239.0.0.1:5554)') + args.add_argument('--ffpath', type=str, default=None, required=False, + help='alternative path to bin (example: /usr/bin/ffmpeg)') + args.add_argument('--watchdog', action='store_true', required=False, + help='detect ffmpeg freeze and terminate') + args.add_argument('--sec', type=int, default=15, required=False, + help='seconds to wait before the watchdog terminates') + args.add_argument('--mono', action='store_true', required=False, + help='detect ffmpeg running copy and terminate') + args = vars(args.parse_args()) + + FFmpeg.run(src=args['src'], preset=args['preset'], fps=args['fps'], dst=args['dst'], + ffpath=args['ffpath'], watchdog=args['watchdog'], sec=args['sec'], mono=args['mono']) diff --git a/cctv-scheduler.conf b/cctv-scheduler.conf index 36acf93..702868f 100644 --- a/cctv-scheduler.conf +++ b/cctv-scheduler.conf @@ -13,11 +13,47 @@ #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 +[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 @@ -30,11 +66,6 @@ nodetype = ds18b20 nodename = 28-1a2b3c4d5e6f -[enable-sequences] -# List the sequence/camera block names. Only blocks with the TRUE value will be used. -camera.test.local = true - - [camera-config:camera.test.local] # Camera parameter description block always starts with "camera-config:". hostname = 192.168.254.253 diff --git a/cctv-scheduler.py b/cctv-scheduler.py index e119e78..6463e41 100644 --- a/cctv-scheduler.py +++ b/cctv-scheduler.py @@ -6,7 +6,10 @@ import urllib.request from argparse import ArgumentParser from datetime import datetime from ftplib import FTP -from os import path, sep, makedirs, remove, replace +from multiprocessing import Process, Queue +from os import path, sep, makedirs, remove, replace, environ +from subprocess import Popen, PIPE, STDOUT +from sys import platform from time import sleep from paramiko import SSHClient, AutoAddPolicy @@ -1126,6 +1129,349 @@ class Sequence: logging.warning(msg='result:' + key + ' = ERROR') +class Proc: + """Find a running process from Python. + """ + @classmethod + 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 + 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(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 + 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(): + 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, + dst: str = None, + fps: int = None, + preset: str = None, + ffpath: str = None, + watchdog: bool = False, + watchsec: int = None, + onlyonce: bool = False + ) -> None: + """Running the installed ffmpeg + + Args: + src (str): sources urls (example: "rtsp://user:pass@host:554/Streaming/Channels/101, anull"). + 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. + ffpath (str, optional): alternative 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 the watchdog terminates. Defaults to None. + onlyonce (bool, optional): detect ffmpeg running copy and terminate. Defaults to False. + """ + + process = ( + cls._bin(ffpath).split() + + cls._src(src).split() + + cls._preset(preset, fps).split() + + cls._dst(dst).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: + print(line, flush=True) + else: + que.put(line) + exit() + + @classmethod + def _bin(cls, path_ffmpeg: str) -> str: + """Returns the path to the ffmpeg depending on the OS. + + Args: + path_ffmpeg (str): alternative path to bin. + + Returns: + str: path to ffmpeg. + """ + 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 archive from: 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 archive from: https://evermeet.cx/ffmpeg/\n' + '\tTarget: /usr/bin/ffmpeg\n' + ) + if not path_ffmpeg: + if platform.startswith('linux') or platform.startswith('darwin'): + path_ffmpeg = '/usr/bin/ffmpeg' + elif platform.startswith('win32'): + path_ffmpeg = environ['PROGRAMFILES'] + "\\ffmpeg\\bin\\ffmpeg.exe" + if path.exists(path_ffmpeg): + return path_ffmpeg + else: + print('ON', platform, 'PLATFORM', 'not found ffmpeg', 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() + + if __name__ == "__main__": time_start = datetime.now() @@ -1138,6 +1484,8 @@ if __name__ == "__main__": ) 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, @@ -1177,7 +1525,51 @@ if __name__ == "__main__": ) logging.getLogger("paramiko").setLevel(logging.WARNING) - if args['sequences']: + 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']) + monopoly = 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') diff --git a/info/images/cctv-scheduler-0.3.png b/info/images/cctv-scheduler-0.3.png new file mode 100644 index 0000000..14c9140 Binary files /dev/null and b/info/images/cctv-scheduler-0.3.png differ