You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2712 lines
88 KiB

#!/bin/sh
# pr-auto-timer - automatic TV timer creation for neutrino
# Copyright (C) 2012-2013 Patrick Reinhardt, pr-cs <at> reinhardtweb.de
# 2013-2014 nadine
# 2014 LtCmdrLuke
# 2013-2017 NI-Team
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
# Related files:
# Configuration: /var/tuxbox/config/pr-auto-timer.conf
# Timer rules: /var/tuxbox/config/pr-auto-timer.rules
# Neutrino plugin: /lib/tuxbox/plugins/pr-auto-timer.{sh,cfg}
VERSION=0.40a
# Changelog:
#
# 0.40a by tewsbso (NI-Team)
# -fix msgbox-calls for larger OSD-resolutions
#
# 0.40 by vanhofen (NI-Team)
# -whitespace cleanup
# -reformatting awk-blocks (just for my readability)
# -change some german words in code
# -search-code in find_show_by_regex() fundamentally revised
# -find_show_by_start_sec() now returns same format as find_show_by_regex()
#
# 0.39 by vanhofen (NI-Team)
# -allow to search for every show; just filter by include/exclude expressions
# To search for every show that contains "Mikrokosmos" in EPG-data use:
# Das Erste HD;*;*,+Mikrokosmos;R
#
# 0.38b by tewsbso (NI-Team)
# -find correct Sky Cinema channel
# add exclude channels: Action Comedy Emotion Family Hits Nostalgie
#
# 0.38a by tewsbo (NI-Team)
# -Bugfix: New movies on Sky Cinema; align to new format on website again
#
# 0.38 by tewsbo (NI-Team)
# -Bugfix: New movies on Sky Cinema; align to new format on website
#
# 0.37b by vanhofen (NI-Team)
# -align to new controlapi calls
#
# 0.37a by vanhofen (NI-Team)
# -bugfix for: find correct Sky Cinema channel
#
# 0.37 by vanhofen (NI-Team)
# -find correct Sky Cinema channel
# -add more Sky Cinema channels
# (in conf-file: SKY_CINEMA="SD|HD|1|1HD|24|24HD[;DOW[,TIMESPAN]][;RECDIR]")
#
# 0.36a by nadine
# -Fixed IIClude Filter One Word (~)
#
# 0.36 by LtCmdrLuke
# -Bugfix: Empty timestamp in some rare cases causes and error on the console (just cosmetic)
# -Bugfix: Fixed log message in dry-run mode about amount of broken records (just cosmetic)
# -Bugfix: Detecting the EXTRA_TIME Before recording is broken in 2.7 (maybe also in 2.6?) due to the introduction of a new value
# ZAPTO_EXTRA_TIME in timerd.conf. This is fixed now.
#
# 0.35 by LtCmdrLuke
# -Bugfix: The path in $ME.show_history is now correctly updated when the index is re-generated and old files have been moved
# -Bugfix/Feature: Should now also work when box is in radio mode. When starting, pr-auto-timer switches the box to tv and afterwards back.
# -Bugfix/workaround: Added a simple function to convert the most common html-entities occurring in titles (&quot; &amp; and &apos;).
# This function uses only shell replacement and is kind of inefficient. Furthermore for full conversion we would need 'recode' or even
# better, neutrino[HD] should be consistent when delivering data in EPG and record-xml.
# -New feature: Added possibility to execute arbitrary pre- and post-actions. This feature can be activated by giving arbitrary commands
# in the new settings in $ME.conf: PRE_ACTION and POST_ACTION. "PRE_ACTION" is executed after initializations, but before processing
# any rules and "POST_ACTION" is executed after processing all rules, before exiting. Updated also $ME.conf.template to reflect changes.
#
# 0.34 by LtCmdrLuke
# -Added command line option "-p|--post-action [x]" to override any configured post-actions with the optional argument x.
# This is helpful, when you configured the box to perform e.g. a reboot after running pr-auto-timer and you need it to
# avoid doing exactly that when you're testing the script.
# -Using command line option -d|--dry-run also prevents any post-action
# -New option BROKEN_RECORD_HANDLING which controls the behavior when detecting broken records while creating the history index;
# see $ME.conf.template for full explanation and possible settings
# -Bugfix when cleaning the history file (if there where more events starting in exactly the same second, only one of them survived the cleaning)
# -Automatically detect and upgrade .show_history prior to 0.33 - no need for any manual intervention.
# -Restructured code, i.e. grouped functions into sections - code is getting bigger and therefore harder to find things. Sections help..
#
# 0.33 by LtCmdrLuke
# -Added possibility to limit the size of $ME.show_history by setting MAX_HISTORY_ENTRIES in the conf file. Default is 1000.
# -Due to the above change, the format of $ME.show_history has changed, there it is necessary to delete the $ME.show_history and let
# pr-auto-timer generate a new one from your files on the disk. If you have manual entries, they must be added again in the new file.
# Use '1500000000' for unknown timestamps (somewhere in the future) and 'x' for unknown filenames.
# -Updated pr-auto-timer.conf.template to reflect this change
# -Improved log-readability by printing human-readable time strings in the most important messages.
#
# 0.32 by LtCmdrLuke
# -Added general handling logic for command-line options, call pr-auto-timer -h for an overview
# -Major changes in the logic for duplicate prevention
# * The additional script gen_show_history is no longer needed - the functionality is fully integrated in pr-auto-timer
# * The gen_show_history.sh-functionality is now accessible with the following command line options:
# * -g|--gen-show-history Generate and print the index from your previous recordings
# * -t|--print-timer-index Generate and print the future timer index.
# * -d|--dry-run Perform a dry-run, i.e. do not add/remove any timers. (provide first!)
# * New config settings (updated also $ME.conf.template to reflect changes)
# * AUTOGEN_SHOW_HISTORY=1 - automatically searches for previously recorded files, when the D-flag is used
# * MYRECORDPATHS - a semicolon separated list of paths to search for previously recorded files
# * Future timers and already existing records are separated now: When using the D-flag and a new timer is added, it is also added to a
# internal future timer index. Therefore manually deleted timers or failed recordings will no longer be detected as duplicates. This
# feature works by reading all timers from the box upon first usage of the D-flag. For each timer, than the EPg is searched for the
# necessary data about the show to compare with new timers.
# -Bugfix: Now the entire info2 is searched for a given regular expression - until now lines between newlines not starting or ending
# with info2 have been omitted
# -Minor
# * Added some quotation to protect special characters from shell in some special situations
# * Re-ordered some code, for better readability and logical reasons
#
# 0.31 by LtCmdrLuke
# -Improved handling of .show_history for duplicate detection:
# * show are stored in $ME.show_history if and only if the D-flag is present
# * removed the N-flag, since the above change makes it obsolete
# * This feature is still not final; handling the size of .show_history and automatic integration of already recorded stuff (i.e. obsoleting gen_show_history) is still missing
# -Bugfix: Added check if $HISTORY_FILE exists before trying to read it.
# -Improved duplicate detection (hopefully) as follows:
# Until now, only the EPG-title and info1 was used to detect the same show again. However, some channels do not set info1 to anything meaningful.
# In this case, pr-auto-timer now uses the first 30 characters (INFO2_CHARACTERS) of info2 to differentiate between shows. Obviously this fails, if the channel EPG
# is changed by the provider for the repetition.
# -Minor improvement of the log-readability
# -Updated the .rules.template files to reflect the newly added flags since 0.30 (W & M)
# -Minor cleanup:
# * Renamed ignore_empty_info --> allow_empty_info (reflects better its purpose)
# * Rewrote parse_flags in a cleaner way (now allows for a log message if unknown flag was used)
#
# 0.30 by nadine
# -Maximale Anzahl an parallelen Aufnahmen parametrierbar zwischen 1 bis 8. Standard: MAX_RECORDS=8
# -History wird bei leerer Kurzbeschreibung (info1) ignoriert. Abschaltbar für z.B. Filme durch Flag 'M'
# -Neues Flag 'N' hinzugefügt. Ist dieses angegeben, werden Duplicate und Flag 'D' ignoriert. Die Aufnahme kommt dann auch nicht ins History-File
# -Neues Flag 'W' hinzugefügt. Da werden ungeachtet der Einstellung von DEL_TIMER_REFRESH Timer wiederholt hinzugefügt.
#
# 0.29 by LtCmdrLuke
# -Extended check for max tuners with transponder information, i.e. now max tuners allows currently for an unlimited number
# of recordings, as long as only at most MAX_TUNERS transponders are needed. The number of concurrent recordings could also be limited
# in future versions - the necessary logic for counting the concurrent recordings is now implicitly available.
# Before adding a timer, pr-auto-timer calculates the needed number of transponders and also the max number of concurrent recordings
# as if the new timer would have been added. Check the logfile to see if timers got rejected because there aren't enough free tuners.
# -Bugfix: define another "ret" (in prevent_timer_by_timespan) as local to prevent side-effects (same effect as the bug wich was fixed in 027)
# -Bugfix: Quotation added to the log-message in parse_dowgroup, to prevent ugly log message
# -Commented unnecessary log-message in parse_dowgroup
# -Added a function to get the transponder-id (tsid) from the service id (sid)
#
# 0.28 by nadine
# -Möglichkeit für die Angabe mehrerer Wochentage
# Um die Tagesschau nur Montags, Mittwochs und Freitags um 20:00 Uhr aufzuzeichnen
# Das Erste HD;MonWedFri,20:00-21:00;Tagesschau
#
# 0.27 by LtCmdrLuke
# -added check if there are enough tuners available before setting a new timer. The number of available tuners can be set
# in the pr-auto-timer.conf with the settings MAX_TUNERS. It defaults to 2.
# If you set this value to a 1 lower, than actually available, your timer recordings will never interfere with your normal
# TV watching. Obviously, if there are to few tuners available, or too many shows at the same time, those cannot be recorded.
# However, if the show is repeated, auto-timer should find the repetition and record it then.
# -code cleanup:
# -renamed some functions to better reflect their purpose for better readability):
# * show_already_recorded --> find_already_recorded
# * add_timer2 --> add_historic_timer
# * find_timer2 --> find_historic_timer
# * find_timer --> find_neutrino_timer
# -declared some function variables local to prevent nasty side-effects (all shell variables are global unless explicitly
# defined local) - this fixes a small bug which lead to adding the show to the show_history, even though the timer was not
# added because it was previously deleted
# -clarified some log messages
#
# 0.26 by LtCmdrLuke
# -prevent recording of repetitions by adding a log of shows which are already covered by timers. The log is located unter
# /var/tuxbox/config/pr-auto-timer.show_history. Currently the description (epg-title) and info1 (usually the episode title
# for tv shows) is used to detect already recorded stuff. Each line of this files works as a ALL word exclude rule.
# -prevent recording can be activated with the flag D:
# NatGeo HD;*;Brain Games;D
# ^
# |
# D=prevent Duplicates
#
# 0.25 by vanhofen (NI-Team)
# -get TV channels in radio mode
# -initialize DEL_TIMER_REFRESH=2 (off)
# -Add possibility to ignore EXTRA_TIME_START|END
# Das Erste HD;*;Tagesschau;I
# ^
# |
# I=Ignore extra time additions.
#
# 0.24 by vanhofen (NI-Team)
# -change 'date' calls to align to older busybox-versions in originalimage
# -add helper function to simulate the nonexisting dos2unix in originalimage
#
# 0.23 by nadine
# -Shutdown hinzugefügt
# Wenn EPGscan nicht gefunden wird, gibt es folgende Optionen:
# END_SHUT_DOWN:[0]-lässt die Box an;1-fährt in Standby;2-fährt in DeepStandBy;3-fährt in PowerOff;4-startet neu
# SHUT_DOWN_ALSO_DAY:[0] - führt SHUT_DOWN nur zwischen 00:00 und 06:00 Uhr durch; 1 - führt SHUT_DOWN immer durch
#
# 0.22 by nadine
# -add_timer2 prüft nun, ob der Eintrag schon besteht.
# -Exclude ergänzt auf JEDES Wort. Wenn eines dieser Wörter gefunden wird, wird die Sendung nicht aufgezeichnet.
# Für Serien, dessen Folgen nur einen kurzen Titel haben. So kann man vorhandene nacheinander angeben.
# Romance TV;*,04:00-18:15;Traumschiff,-Oman Bali Malaysia Vegas Savannah
# -Include ergänzt auf JEDES Wort. Wenn eines dieser Wörter gefunden wird, wird die Sendung aufgezeichnet.
# Für Serien, dessen Folgen nur einen kurzen Titel haben. So kann man vorhandene nacheinander angeben.
# Romance TV;*,04:00-18:15;Traumschiff,~Oman Bali Malaysia Vegas Savannah
# -Weitere Paramater in der Config Datei werden ausgewertet (Für Funktion aus V0.21 (gelöschte Timer werden nicht mehr gesetzt)
# MAX_TMR2_COUNT=250 # Behält max. 250 Einträge in der Datei /var/tuxbox/config/pr-auto-timer.tmr beim aufräumen (Standard:250)
# DEL_TIMER_REFRESH=2 #Schaltet die Funktion ganz ab. (Standard:0)
# DEL_TIMER_REFRESH=1 #Führt im Hintergrund die Datei weiter, um nach wiedereinschalten sofort arbeiten zu können. (Standard:0)
# -Include Filter werden ignoriert, wenn keine Beschreibung gefunden wurde
#
# 0.21 by nadine
# -Groß- Kleinschreibung bei Sendungstitel egal
# -Include Wörter ermöglicht. Diese können ANSTATT der Exclude Wörter genutzt werden. Statt des ! kommt ein +
# Das Erste HD;*;Tagesschau,+include
# -einmal gesetzte Timer werden nicht nochmal gesetzt, auch wenn diese zwischenzeitlich manuell gelöscht wurden
# sollen alle wieder gesetzt werden, dann die Datei /var/tuxbox/config/pr-auto-timer.tmr löschen
# Einträge, dessen Stopzeit vor der aktuellen Zeit liegen, werden automatisch wieder entfernt, um die Dateigröße
# nicht unnötig ansteigen zu lassen
#
# 0.20 by vanhofen (NI-Team)
# -new movies at "Sky Cinema" can be recorded at spec. days/times
# (in conf-file: SKY_CINEMA="HD|SD|1|24[;DOW[,TIMESPAN]][;RECDIR]")
# RECDIR is optional; default is the neutrino setting
#
# 0.19 by vanhofen (NI-Team)
# -fix function to trim strings
# -fix function to get channels from bouquet
#
# 0.18 by vanhofen (NI-Team)
# -move pr-auto-timer from /var/plugins to /lib/tuxbox/plugins
# -show pr-auto-timer as a script plugin in neutrino
# -reformatting awk-blocks (just for my readability)
# -remove variables to binarys
# -search for shows and informations word by word
# -ignore case in exclusion words
# -search for exclusion words in detailed informations too
# -add possibility to record all new movies at "Sky Cinema"
# -support for external rules-file
# (in conf-file: RULE_FILE_EXT=/path/to/file)
#
# 0.17 by vanhofen (NI-Team)
# -accept day groups "Weekday" and "Weekend"
# -small code optimizations
# -accept uppercase flags only
# -Add possible exlusion expressions
# FOX HD;*;LOST,!Special
#
# -Support for alternative recording directorys
# NOTE: flag-section can be empty, but MUST be defined!
# Sky Cinema HD;*;Ice Age;;/mnt/rec
#
# -Add possibility to create only the timer for the first show
# Das Erste HD;*;Tagesschau;F
# ^
# |
# F=Create first timer only.
#
# -Add possibility to deactivate an entry in rules-file on success (Danke, Patrick!)
# Sky Cinema HD;*;Ice Age;O
# ^
# |
# O=Create first timer only and deactivate rule
#
# NOTE: Define multiple flags by separating them with commas
# Sky Cinema HD;*;Ice Age;O,Z
#
# 0.16 by vanhofen (NI-Team)
# -Whitespace cleanup
# -Ignore case at search for channel-name and timers
# -Add timespan-support
#
# 0.15 by Patrick Reinhardt
# -Documentation
# -Invalid channel names (including those with wrong upper/lower case) now produce a warning message in the log. (Danke, nadine)
# -Added debugging option DEBUG_DISPLAYLOG
# -License is now GPLv2
#
# 0.14 by Patrick Reinhardt
# -License is now GPL
# -Documentation
# -Code cleanup & optimizations
#
# 0.13 by Patrick Reinhardt
# -Updated debugging options
# -Code optimizations
# -Fixed special character handling in find_timer, remove_overlapping_timer
# -Fixed a bug in remove_overlapping_timer
#
# 0.12 by Patrick Reinhardt
# -EPG data is now acquired using the channel id rather than the channel name as a workaround for a neutrino query string bug. (Danke, tewsbo)
#
# 0.11 by Patrick Reinhardt
# -Code cleanup & optimizations
# -Fixed zap timers (Danke, tewsbo)
# -Default log path changed to /tmp
# -LOG_FILE can now be overridden in the configuration file (Setting to 'off' disables logging. Default if unset or invalid: /tmp/pr-auto-timer_DATE.log
# -RULE_FILE can now be overridden in the configuration file. Default if unset or invalid: /var/tuxbox/config/pr-auto-timer.rules
#
# 0.10 by Patrick Reinhardt
# -Bugfix in the comments :)
# -Added possibility to add zapto timers.
# Das Erste HD;Mon;Pinguin;Z
# ^
# |
# Z=Create zap timer instead of record.
# Backwards compatibility is ensured, i. e. the old format will continue to work
#
# 0.09 by Patrick Reinhardt
# -Cleaned up unused code
# -Several bugfixes in the changelog :)
# -Check if script is already running
# -Added command line option '--menu'/'-m' which displays a popup in Neutrino
#
# 0.08 by Patrick Reinhardt
# -Any web server configuration should be supported now. (Danke, vanhofen)
# -Handle non existing timerd.conf. (Danke, vanhofen)
# -Timers file (pr-auto-timer.conf) moved to /var/tuxbox/config/pr-auto-timer.rules
# -Config (MAX_DIFF_MINS) moved to new config file /var/tuxbox/config/pr-auto-timer.conf
# -Removed warning if log path does not exist
#
# 0.07 by Patrick Reinhardt
# -Cleaned up unused code
# -It is now possible to handle whole bouquets rather than single channels
# -EPG data is now cached
# -Added a warning message in case the log path does not exist.
# -Minor optimizations (Danke, vanhofen)
#
# 0.06 by Patrick Reinhardt
# -Fixed day of week filter (Danke, fred_feuerstein)
# -RECORD_CORRECTION_MINS removed, recording times now corrected using the values of the tuxbox menu.
# -Reformatted log.
NAME="Auto-Timer"
ME=${0##*/}
CONFIG_FILE=/var/tuxbox/config/$ME.conf
PID_FILE=/var/run/$ME.pid
TMR2_TEMPFILE=/tmp/$ME.tmr
TMR2_FILE=/var/tuxbox/config/$ME.tmr
EXIT_SIGNAL=1
EXIT_NO_RULE_FILE=2
EXIT_ALREADY_RUNNING=3
NEUTRINO_CONF="/var/tuxbox/config/neutrino.conf"
TIMERD_EVENT_TYPE_ZAPTO=3
TIMERD_EVENT_TYPE_RECORD=5
# How many characters of the EPG-info2 field should be (at most)
# considered, if info1 is empty or the same as the title
INFO2_CHARACTERS=30
# The current version for entries in the $ME.show_history-file
EVENT_INDEX_VERSION=1
# The limit under which a recorded transport stream is considered broken, given in kb
BROKEN_FILE_SIZE_LIMIT=2
#Debugging options
#Do not remove temporary directory:
DEBUG_TEMP=0
#Do not add or remove any timers:
DEBUG_DRY_RUN=0
#Output display log to STDOUT:
DEBUG_DISPLAYLOG=0
# Default values for variables from the config file
HISTORY_FILE=/var/tuxbox/config/$ME.show_history
MYRECORDPATHS=std::neutrino
DEL_TIMER_REFRESH=2
#######################################################################################
#BEGIN SECTION "Helper functions"
signal_handler() {
#Handle INT, TERM signals and clean up.
log "Caught signal. Cleaning up."
cleanup
set +f
log "done."
exit $EXIT_SIGNAL
}
cleanup() {
local result
# first set the box back to the previous mode (in case it was in radio before starting)
if [ "$box_mode" == "radio" ]; then
log "\tcleanup: Switching box back to $box_mode .. "
result=$(wget -q -O - $webserver_url/control/setmode?$box_mode | dos2unix)
if [ "$result" != "ok" ]; then
log "\tcleanup: Could not switch box to $box_mode: $result"
else
log "\tcleanup: Successfully switched box to $box_mode (Result: $result)."
fi
# set also the standby-mode back, since switching to radio deactivates standby...
if [ "$box_standby" == "on" ]; then
result=$(wget -q -O - $webserver_url/control/standby?on | dos2unix)
if [ "$result" != "ok" ]; then
log "\tcleanup: Could not reactivate standby: $result"
else
log "\tcleanup: Successfully reactivated standby after mode switching (Result: $result)."
fi
fi
fi
#Remove temporary directory
if [ $DEBUG_TEMP == 0 ]; then
log "\tcleanup: Removing directory '$temp_dir'..."
rm -rf $temp_dir 2>/dev/null
fi
# now remove also the pid-file
rm -rf $PID_FILE 2>/dev/null
}
log() {
#Log message to log file
#$*: Log message
if [ "$LOG_FILE" != "" ]; then
echo -e $(date +'%F %H:%M:%S') [$$]: "$*" >> $LOG_FILE
fi
}
displaylog() {
#Log message to display log (--menu-mode)
#$*: Log message
# why? if [ $opt_menu ]; then
echo -e "$@" >> $displaylogfile
# fi
}
trim() {
# helper function to strip unnecessary spaces from string
echo "$@" | sed 's/^ *//g;s/ *$//g;s/ \{1,\}/ /g'
}
dos2unix() {
# helper function to simulate dos2unix
sed 's/\r$//'
}
begin_ifs_block() {
#Backup IFS (input field separator) to restore it after parsing arguments
IFS_SAVE=$IFS
set -f
}
end_ifs_block() {
#Restore (input field separator) IFS after parsing arguments
IFS=$IFS_SAVE
set +f
}
url_encode() {
#http://rosettacode.org/wiki/URL_encoding#AWK
echo $*|awk '
BEGIN {
for (i = 0; i <= 255; i++)
ord[sprintf("%c", i)] = i
}
function escape(str, c, len, res) {
len = length(str)
res = ""
for (i = 1; i <= len; i++) {
c = substr(str, i, 1);
if (c ~ /[0-9A-Za-z]/)
res = res c
else
res = res "%" sprintf("%02X", ord[c])
}
return res
}
{ print escape($0) }
'
}
html2text() {
# Helper function to convert html encoding (&value;) into their characters
# Only the most common html entities are converted. For full conversion
# we would need 'recode'
# Reads html encoding (can be multiple lines) from std input and
# returns the same text ASCII encoded.
while read line; do
res=${line//&quot;/\"}
res=${res//&apos;/\'}
res=${res//&amp;/\&}
#res=${res//&gt;/\>}
#res=${res//&lt;/\<}
# ...other entites, but use as few as possible, since this is inefficient.
echo "$res"
done
}
get_corrected_start_stop_times() {
#Calculate corrected start/stop times using the tuxbox values (EXTRA_ etc.)
#$1: start secs
#$2: stop secs
#$3: timerd event type
#returns: $start_sec_corrected $stop_sec_corrected
if [ $3 = $TIMERD_EVENT_TYPE_ZAPTO ]; then
start_sec_corrected=$(( $1 - $zap_correction_secs_before ))
stop_sec_corrected=0
else
if [ "$ignore_record_correction" = "false" ]; then
start_sec_corrected=$(( $1 - $record_correction_secs_before ))
stop_sec_corrected=$(( $2 + $record_correction_secs_after ))
else
start_sec_corrected=$1
stop_sec_corrected=$2
fi
fi
echo $start_sec_corrected $stop_sec_corrected
}
get_tsid_from_cid() {
# $1: channel_id
#returns: id of the transponder
echo ${1:4:4}
}
get_setting()
{
test -e $NEUTRINO_CONF && \
grep "^${1}=" $NEUTRINO_CONF | cut -d'=' -f2
}
scale2res()
{
value=${1:-0}
if [ $osd_resolution -eq 1 ]; then
echo -ne $value | awk '{ printf("%04.0f", $1 + $1/2) }'
else
echo -ne $value
fi
}
#END SECTION "Helper functions"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Initialization"
init_config() {
#Parse config file (default: /var/tuxbox/config/pr-auto-timer.conf)
if [ -e $CONFIG_FILE ]; then
source $CONFIG_FILE 2>/dev/null
fi
# Initialize the logfile first, so we can write to it...
if [ ! -d "${LOG_FILE%/*}" ]; then
case $LOG_FILE in
[oO][fF][fF])
LOG_FILE=''
;;
*)
LOG_FILE=/tmp/${ME}_$(date +'%F').log
;;
esac
fi
echo -e "\n\n========================== New log started at $(date) ======================================" >> $LOG_FILE
# Check the settings and reset them to default if unset or invalid
case $MAX_DIFF_MINS in
''|*[!0-9]*)
MAX_DIFF_MINS=10
log "\tinit_config: MAX_DIFF_MINS not found or invalid. Setting default MAX_DIFF_MINS=$MAX_DIFF_MINS."
;;
esac
case $MAX_TMR2_COUNT in
''|*[!0-9]*)
MAX_TMR2_COUNT=250
log "\tinit_config: MAX_TMR2_COUNT not found or invalid. Setting default MAX_TMR2_COUNT=$MAX_TMR2_COUNT."
;;
esac
case $END_SHUT_DOWN in
''|*[!0-9]*)
END_SHUT_DOWN=0
log "\tinit_config: END_SHUT_DOWN not found or invalid. Setting default END_SHUT_DOWN=$END_SHUT_DOWN."
;;
esac
case $SHUT_DOWN_ALSO_DAY in
''|*[!0-9]*)
SHUT_DOWN_ALSO_DAY=0
log "\tinit_config: SHUT_DOWN_ALSO_DAY not found or invalid. Setting default SHUT_DOWN_ALSO_DAY=$SHUT_DOWN_ALSO_DAY."
;;
esac
case $MAX_TUNERS in
''|*[!0-9]*)
MAX_TUNERS=2
log "\tinit_config: MAX_TUNERS not found or invalid. Setting default MAX_TUNERS=$MAX_TUNERS."
;;
esac
case $MAX_RECORDS in
''|[!1-8])
MAX_RECORDS=8
log "\tinit_config: MAX_RECORDS not found or invalid. Setting default MAX_RECORDS=$MAX_RECORDS."
;;
esac
case $AUTOGEN_SHOW_HISTORY in
''|[!0-1])
AUTOGEN_SHOW_HISTORY=1
log "\tinit_config: AUTOGEN_SHOW_HISTORY not found or invalid. Setting default AUTOGEN_SHOW_HISTORY=$AUTOGEN_SHOW_HISTORY."
;;
esac
case $BROKEN_RECORD_HANDLING in
''|[!0-2])
BROKEN_RECORD_HANDLING=1
log "\tinit_config: BROKEN_RECORD_HANDLING not found or invalid. Setting default BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING."
;;
esac
case $MYRECORDPATHS in
'')
MYRECORDPATHS=std::neutrino
log "\tinit_config: MYRECORDPATHS not found or invalid. Setting default MYRECORDPATHS=std::neutrino."
;;
esac
case $MAX_HISTORY_ENTRIES in
''|*[!0-9]*)
MAX_HISTORY_ENTRIES=1000
log "\tinit_config: MAX_HISTORY_ENTRIES not found or invalid. Setting default MAX_HISTORY_ENTRIES=$MAX_HISTORY_ENTRIES."
;;
esac
if [ ! -e "$RULE_FILE" ]; then
RULE_FILE=/var/tuxbox/config/$ME.rules
if [ ! -e "$RULE_FILE" ]; then
echo "ERROR: Rules file '$RULE_FILE' does not exist! Exiting."
log "ERROR: Rules file '$RULE_FILE' does not exist! Exiting."
exit $EXIT_NO_RULE_FILE
fi
fi
# support for external rule-file
if [ ! -e "$RULE_FILE_EXT" ]; then
RULE_FILE_EXT=""
fi
# Prepare some internal variables
RULE_FILES="$RULE_FILE $RULE_FILE_EXT"
max_diff_secs=$(( $MAX_DIFF_MINS \* 60 ))
}
init_temp() {
#Init temporary directory to cache EPG data etc.
temp_dir=$(mktemp -dt $ME.XXXXXX)
log "\tinit_temp: Created temporary directory '$temp_dir'."
}
init_displaylog() {
#Init display log file used for --menu-mode
displaylogfile="$temp_dir/display.log"
}
init_settings() {
#Init Tuxbox settings (record/zap extra time)
record_correction_secs_before=0
record_correction_secs_after=0
zap_correction_secs_before=0
neutrino_rec_dir=""
if [ -f /var/tuxbox/config/timerd.conf ]; then
record_correction_secs_before=$(egrep "^EXTRA_TIME_START" /var/tuxbox/config/timerd.conf | cut -d'=' -f2)
record_correction_secs_after=$(egrep "^EXTRA_TIME_END" /var/tuxbox/config/timerd.conf | cut -d'=' -f2)
fi
if [ -f /var/tuxbox/config/neutrino.conf ]; then
zap_correction_secs_before=$(( $(egrep zapto_pre_time /var/tuxbox/config/neutrino.conf | cut -d'=' -f2) * 60 ))
neutrino_rec_dir=$(egrep network_nfs_recordingdir /var/tuxbox/config/neutrino.conf | cut -d'=' -f2)
fi
log "\tinit_settings: record_correction_secs_before=$record_correction_secs_before, record_correction_secs_after=$record_correction_secs_after"
log "\tinit_settings: zap_correction_secs_before=$zap_correction_secs_before"
log "\tinit_settings: neutrino_rec_dir=$neutrino_rec_dir"
osd_resolution=$(get_setting "osd_resolution")
}
init_webserver() {
#Build web server URL for API access including authentication
#returns: web server base URL, e. g. http://user:pass@localhost
webserver_url=$(awk '
BEGIN {
FS="=";
}
/^WebsiteMain.port/ {
port=$2;
}
/^mod_auth.authenticate=/ {
auth=$2;
}
/^mod_auth.username=/ {
username=$2;
}
/^mod_auth.password=/ {
password=$2;
}
END {
if ( auth == "true" ) {
printf("http://%s:%s@localhost:%i\n", username, password, port);
}
else {
printf("http://localhost:%i\n", port);
}
}
' /var/tuxbox/config/nhttpd.conf)
}
#END SECTION "Initialization"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Command line options handling"
parse_options() {
#Parse pr-auto-timer command line arguments
local option
while [ $# -gt 0 ]
do
option=$1
shift
case "$option" in
-m|--menu)
opt_menu=true
;;
-g|--gen-show-history)
log "pr-auto-timer V$VERSION generating/updating your event history index file ($HISTORY_FILE)"
init_settings
AUTOGEN_SHOW_HISTORY=1
generate_history_index
if [ $DEBUG_DRY_RUN -eq 0 ]; then
clean_history_file
else
log "DEBUG DRY RUN: Index created, but NOT updating your index file - printing the result on console instead"
echo -e "The following events are already available and will\nbe ignored when planing new timers with the D-flag:"
echo -e "---------8><-------------"
cat $HISTORY_IDX_TMP_FILE
echo -e "---------8><-------------"
fi
cleanup
log "pr-auto-timer V$VERSION is done and exiting happily."
exit 0
;;
-d|--dry-run)
log "\tparse_options: Debug dry-run activated on command line."
DEBUG_DRY_RUN=1
;;
-p|--post-action)
if [ -n "$1" ] && [ ! "${1:0:1}" == "-" ] && [ $1 -ge 0 ] && [ $1 -le 4 ]; then
log "\tparse_options: post-actions overridden on command line with END_SHUT_DOWN=$1"
END_SHUT_DOWN=$1
shift
else
log "\tparse_options: post-actions deactivated on command line."
END_SHUT_DOWN=0
fi
;;
-t|--print-timer-index)
log "pr-auto-timer V$VERSION generating future timer index."
init_settings
init_webserver
generate_timer_index
echo -e "The following events are currently programmed to be recorded and will\nbe ignored when planing new timers with the D-flag:"
echo -e "---------8><-------------"
cat $TIMER_IDX_TMP_FILE
echo -e "---------8><-------------"
cleanup
log "pr-auto-timer V$VERSION is done."
exit 0
;;
-h|--help)
usage
cleanup
exit 0
;;
*)
echo "Unknown command line option '$option'. What did you want to do? Exiting!"
usage
cleanup
exit 1
;;
esac
done
}
usage() {
# Print short usage message on screen
echo -e "Usage: $ME [options]"
echo -e "Valid options are:"
echo -e "\t-m|--menu\t\tShow menu in neutrino."
echo -e "\t-g|--gen-show-history\tGenerate and print the index from your previous recordings (and exit)."
echo -e "\t-t|--print-timer-index\tGenerate and print the future timer index (and exit)."
echo -e "\t-p|--post-action [x]\tOverride configured post-action with the given argument x=[0..4].\n\t\t\t\tIf no argument is given the post-action is deactivated, i.e x=0."
echo -e "\t-d|--dry-run\t\tPerform a dry-run, i.e. do not add/remove any timers. (provide first!)"
echo -e "\t-h|--help\t\tPrint this help and exit."
}
#END SECTION "Command line options handling"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Rule processing"
parse_regexes() {
#Parse search regexes
regex=$(trim "${regex}")
xregex=$(trim ${xregex})
xregex_option=""
if [ ${#xregex} -le 1 ]; then
xregex=""
# tricking shell with an uppercase X
# to avoid interpreting +, ~, ! and - as an operator
# and trim again
elif [ "X${xregex:0:1}" = "X+" ]; then
xregex="${xregex:1}"
xregex=$(trim ${xregex})
xregex_option="INCLUDE_ALL"
elif [ "X${xregex:0:1}" = "X~" ]; then
xregex="${xregex:1}"
xregex=$(trim ${xregex})
xregex_option="INCLUDE_ONE"
elif [ "X${xregex:0:1}" = "X!" ]; then
xregex="${xregex:1}"
xregex=$(trim ${xregex})
xregex_option="EXCLUDE_ALL"
elif [ "X${xregex:0:1}" = "X-" ]; then
xregex="${xregex:1}"
xregex=$(trim ${xregex})
xregex_option="EXCLUDE_ONE"
else
regex="$regex , $xregex" # no +, ~, ! or - found; add to regex again (the comma too)
xregex=""
fi
}
parse_timespan() {
#Parse possible timespan
timespan="$1"
if [ "$timespan" = "" ]; then
timespan="*"
return
fi
#split timespan
begin_ifs_block
IFS='-'
set -- $timespan
timespan_start=$1
timespan_stop=$2
end_ifs_block
#split timespan_start
begin_ifs_block
IFS=':'
set -- $timespan_start
timespan_start_h=$1
timespan_start_m=$2
end_ifs_block
#split timespan_stop
begin_ifs_block
IFS=':'
set -- $timespan_stop
timespan_stop_h=$1
timespan_stop_m=$2
end_ifs_block
e=0
#check format hh:mm
echo $timespan_start | grep -q '^[0-9][0-9]:[0-9][0-9]'
e=$(($e+$?))
echo $timespan_stop | grep -q '^[0-9][0-9]:[0-9][0-9]'
e=$(($e+$?))
#check hours
if [ "$timespan_start_h" -gt "23" -o "$timespan_stop_h" -gt "23" ]; then
e=$(($e+1))
fi
#check minutes
if [ "$timespan_start_m" -gt "59" -o "$timespan_stop_m" -gt "59" ]; then
e=$(($e+1))
fi
#finally we use the date-command to check the full string
date -d $timespan_start +%s 2>/dev/null 1>/dev/null
e=$(($e+$?))
date -d $timespan_stop +%s 2>/dev/null 1>/dev/null
e=$(($e+$?))
if [ $e -ne 0 ]; then
log "\tparse_timespan: Error while checking! timespan disabled!"
timespan="*"
else
timespan_start_s=$(date -d $timespan_start +%s)
timespan_stop_s=$(date -d $timespan_stop +%s)
if [ $timespan_start_s -gt $timespan_stop_s ]; then
timespan_over_midnight=true
else
timespan_over_midnight=false
fi
fi
}
parse_flags() {
#Parse flags of entries in rule file
event_type=$TIMERD_EVENT_TYPE_RECORD
first_match=false
only_once=false
ignore_record_correction=false
prevent_duplicates=false
ignore_timer_refresh=false
allow_empty_info=false
# split flags with the separator ","
begin_ifs_block
IFS=','
for flag in $1; do
#log "\tmain: Found flag $flag!"
case $flag in
Z)
event_type=$TIMERD_EVENT_TYPE_ZAPTO
;;
R)
event_type=$TIMERD_EVENT_TYPE_RECORD
;;
F)
first_match=true
;;
O)
only_once=true
first_match=true
;;
I)
ignore_record_correction=true
;;
D)
prevent_duplicates=true
;;
W)
ignore_timer_refresh=true
;;
M)
allow_empty_info=true
;;
*)
log "\tparse_flags: WARNING - Unknown flag $flag found; Ignoring!"
;;
esac
done
end_ifs_block
}
parse_recdir() {
#Parse recording directory
if [ $event_type = $TIMERD_EVENT_TYPE_ZAPTO ]; then
recdir=""
else
recdir=$(url_encode "$recdir")
fi
}
parse_dowgroup() {
#Check for weekday or weekend and set var dowgroup
local result _dow
dowgroup=$(echo "$dowfilter" | awk '{print tolower($0)}')
_dow=$(echo $dow | awk '{print tolower($0)}')
if [ "$dowgroup" = "weekday" ]; then
dowgroup="montuewedthufri"
fi
if [ "$dowgroup" = "weekend" ]; then
dowgroup="satsun"
fi
result=$(awk -v _dowgroup="$dowgroup" -v _dow="$_dow" 'BEGIN {print match(_dowgroup, _dow); exit 0 }')
if [ $result -gt 0 ]; then
dowgroup=true
fi
#log "\t\tparse_dowgroup: dowgroup='$dowgroup', dow='$_dow'"
}
#END SECTION "Rule processing"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Retrieve & Search EPG"
get_channel_id_by_channel_name() {
#$1: channel name (ignore case)
#returns: id of the first(!) matching channel
temp_file="$temp_dir/channellist"
if [ ! -e "$temp_file" ]; then
wget -qO "$temp_file" "$webserver_url/control/channellist"
fi
awk -v channel="$1" '
/^[0-9a-f]{16} / {
if (tolower($0) == tolower($1 " " channel)) {
print $1;
exit 0;
}
}
' $temp_file
#Nooooooooooooo! This wouldn't have been necessary if query string handling worked in Neutrino...
}
get_channel_xml() {
#Fetch EPG data in XML for a specified channel.
#$1: channel name
#returns: XML EPG data
channel_id=$(get_channel_id_by_channel_name "$1")
if [ "$channel_id" != "" ]; then
temp_file="$temp_dir/$channel_id"
if [ ! -e "$temp_file" ]; then
wget -qO "$temp_file" "$webserver_url/control/epg?xml=true&details=true&channelid=$channel_id"
fi
cat "$temp_file"
#log "\t\tget_channel_xml: Retrieved EPG from channel '$1' (CID: $channel_id TSID: $(get_tsid_from_cid "$channel_id")) "
else
log "\t\tget_channel_xml: !!! WARNING: Channel '$1' was NOT found. Skipping !!!"
fi
}
get_channels_from_bouquet() {
#$1: bouquet number
#returns: channel names
_channels=$(wget -qO - "$webserver_url/control/getbouquet?bouquet=${1}&mode=TV" | dos2unix)
echo "$_channels" | cut -d" " -f3-
}
find_show() {
#Find a show by (exact) channel name or bouquet number and RegEx(!) for show name.
#$1: channel
#$2: show name regex
#$3: extended regex
#$4: extended regex option
#returns: list of found shows in the format 'CHANNEL_ID|START_SEC|STOP_SEC|CHANNEL_NAME|DESCRIPTION|INFO1|INFO2'
BC="$1"
case ${BC:0:1} in
"*")
if [ "$1" = "*" ]; then
bouquet=1
else
bouquet=${BC:1}
fi
log "\t\tfind_show: Bouquet: $bouquet"
channels=$(get_channels_from_bouquet $bouquet)
echo -e "$channels" | while read channel; do
find_show_by_regex "$channel" "$2" "$3" "$4"
done
;;
*)
find_show_by_regex "$1" "$2" "$3" "$4"
;;
esac
}
find_show_by_regex() {
#Find a show by (exact) channel name and RegEx(!) for show name.
#$1: channel name
#$2: show name regex
#$3: extended regex
#$4: extended regex option
#returns: list of found shows in the format 'CHANNEL_ID|START_SEC|STOP_SEC|CHANNEL_NAME|DESCRIPTION|INFO1|INFO2'
local xml result matches
xml=$(get_channel_xml "$1")
result=$(echo -ne "$xml"|awk '
BEGIN {
false=0;
true=1;
matches=0;
}
END {
exit (matches == 0);
}
/<prog>/ {
channel_id = "";
start_sec = "";
stop_sec = "";
description = "";
info1 = "";
info2_1 = "";
info2_2 = "";
info2_open = 0;
}
/<channel_id>[0-9a-z]+<\/channel_id>/ {
channel_id = gensub(/<\/?[^>]+>/, "", "g");
}
/<start_sec>[0-9]+<\/start_sec>/ {
start_sec = gensub(/<\/?[^>]+>/, "", "g");
}
/<stop_sec>[0-9]+<\/stop_sec>/ {
stop_sec = gensub(/<\/?[^>]+>/, "", "g");
}
/<info1>.*<\/info1>/ {
info1 = gensub(/(<info1><!\[CDATA\[|\]\]><\/info1>)/, "", "g");
}
/<info2>.*/ {
info2_1 = gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
info2_open = 1;
}
/.*<\/info2>/ {
info2_2 = gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
info2_open = 0;
}
# This 'captures' also lines inbetween newlines for info2
!/.*info2.*/ {
if (info2_open)
{
info2_1=info2_1 " " $0
}
}
/<description><!\[CDATA\[.*\]\]><\/description>/ {
description = gensub(/(<description><!\[CDATA\[|\]\]><\/description>)/, "", "g");
}
/<\/prog>/ {
if (info2_1 == info2_2)
{
info2 = info2_1;
}
else
{
info2 = info2_1 " " info2_2;
}
# ignore case in fulldescription
fulldescription = tolower(description);
if (length(info1) > 0)
{
fulldescription = tolower(fulldescription " " info1);
}
if (length(info2) > 0)
{
fulldescription = tolower(fulldescription " " info2);
}
info_length = length(info1) + length(info2);
find_type = ""
include_regex = ""
exclude_regex = ""
if (match(extended_regex_option, "INCLUDE"))
{
find_type = "include"
include_regex = tolower(extended_regex);
}
else if (match(extended_regex_option, "EXCLUDE"))
{
find_type = "exclude"
exclude_regex = tolower(extended_regex);
}
include_regex = tolower(include_regex);
exclude_regex = tolower(exclude_regex);
find_option = ""
if (match(extended_regex_option, "ALL"))
{
find_option = "all";
}
else if (match(extended_regex_option, "ONE"))
{
find_option = "one";
}
# split into arrays
split(show_regex, show_array);
split(include_regex, include_array);
split(exclude_regex, exclude_array);
show_found = false;
show_counter = 0;
show_matches = 0;
if (show_regex == "*")
{
# no need to search; simply grab the show
show_counter = 1;
show_matches = 1;
}
else
{
for (i in show_array)
{
# search for every regex/word from show_regex in description only
show_counter++;
if (match(string tolower(description), string tolower(show_array[i])))
{
show_matches++;
}
}
}
if ( (show_matches > 0) && (show_counter == show_matches) )
{
# all search words or "*" found
show_found = true;
if (find_type == "exclude")
{
exclude_counter = 0;
exclude_matches = 0;
for (i in exclude_array)
{
# search for every regex/word from exclude_regex in fulldescription
exclude_counter++;
if (match(fulldescription, exclude_array[i]))
{
exclude_matches++;
}
}
if (find_option = "all")
{
if ( (exclude_matches > 0) && (exclude_counter == exclude_matches) )
{
# all exclude words found; mark this show as "false"
show_found = false;
}
}
else if (find_option = "one")
{
if ( (exclude_matches > 0) && (exclude_counter >= 1) )
{
# one exclude word found; mark this show as "false"
show_found = false;
}
}
}
else if (find_type == "include")
{
include_counter = 0;
include_matches = 0;
for (i in include_array)
{
# search for every regex/word from include_regex in fulldescription
include_counter++;
if (match(fulldescription, include_array[i]))
{
include_matches++;
}
}
if (find_option = "all")
{
if ( (include_counter > 0) && (include_counter != include_matches) )
{
# not all include words found; mark this show as "false"
show_found = false;
}
}
else if (find_option = "one")
{
if ( (include_counter > 0) && (include_matches < 1) )
{
# not any include word found; mark this show as "false"
show_found = false;
}
}
}
}
if (show_found == true)
{
matches++;
printf("%s|%i|%i|%s|%s|%s|%s\n", channel_id, start_sec, stop_sec, channel_name, description, info1, info2);
}
}
' channel_name="$1" show_regex="$2" extended_regex="$3" extended_regex_option="$4")
if [ $? -eq 0 ]; then
matches=$(echo "$result"|wc -l)
log "\tfind_show_by_regex: Found $matches matches for regex '$2' (extended regex '$3', option '$4') on channel '$1'."
echo -e "$result"
#log "\t\tfind_show_by_regex: Result: '$result'"
return 0
else
log "\tfind_show_by_regex: Found NO matches for regex '$2' (extended regex '$3', option '$4') on channel '$1'."
return 1
fi
}
find_show_by_start_sec() {
#Find a show by (exact) channel name and start time.
#$1: channel name
#$2: start time in seconds
#returns: list of found shows in the format 'CHANNEL_ID|START_SEC|STOP_SEC|CHANNEL_NAME|DESCRIPTION|INFO1|INFO2'
xml=$(get_channel_xml "$1")
result=$(echo -ne "$xml"|awk '
BEGIN {
matches=0;
}
END {
exit (matches == 0);
}
/<prog>/ {
channel_id = "";
start_sec = "";
stop_sec = "";
description = "";
info1 = "";
info2_1 = "";
info2_2 = "";
info2_open = 0;
}
/<channel_id>[0-9a-z]+<\/channel_id>/ {
channel_id = gensub(/<\/?[^>]+>/, "", "g");
}
/<start_sec>[0-9]+<\/start_sec>/ {
start_sec = gensub(/<\/?[^>]+>/, "", "g");
}
/<stop_sec>[0-9]+<\/stop_sec>/ {
stop_sec = gensub(/<\/?[^>]+>/, "", "g");
}
/<info1>.*<\/info1>/ {
info1 = gensub(/(<info1><!\[CDATA\[|\]\]><\/info1>)/, "", "g");
}
/<info2>.*/ {
info2_1 = gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
info2_open = 1;
}
/.*<\/info2>/ {
info2_2 = gensub(/(<info2><!\[CDATA\[|\]\]><\/info2>)/, "", "g");
info2_open = 0;
}
# This 'captures' also lines inbetween newlines for info2
!/.*info2.*/ {
if (info2_open)
{
info2_1=info2_1 " " $0
}
}
/<description><!\[CDATA\[.*\]\]><\/description>/ {
description = gensub(/(<description><!\[CDATA\[|\]\]><\/description>)/, "", "g");
}
/<\/prog>/ {
if (info2_1 == info2_2)
{
info2 = info2_1;
}
else
{
info2 = info2_1 " " info2_2;
}
if ((search_start_sec == start_sec) || (search_start_sec == start_sec-record_correction_secs_before) )
{
matches=1;
printf("%s|%i|%i|%s|%s|%s|%s\n", channel_id, start_sec, stop_sec, channel_name, description, info1, info2);
}
}
' channel_name="$1" search_start_sec="$2" record_correction_secs_before="$record_correction_secs_before")
if [ $? -eq 0 ]; then
log "\t\tfind_show_by_start_sec: Found match for start time '$2' on channel '$1'."
echo -e "$result"
return 0
fi
}
#END SECTION "Retrieve & Search EPG"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Event & Timer Index"
read_history_index() {
# Reads and upgrades the event index from the configured file $HISTORY_FILE.
# No parameters
# Returns: The read and upgraded index is available in the temporary file $HISTORY_IDX_TMP_FILE
local line title info timestamp new_timestamp filename
# First remove the temporary file, in case it exists - this shouldn't happen, but lets be safe.
rm -f "$HISTORY_IDX_TMP_FILE" 2>/dev/null
# Now read the configured HISTORY FILE (if it exists) and upgrade it if necessary.
if [ -e $HISTORY_FILE ]; then
# With the version property, future upgrades of the format are possible
# It is currently (v0.34) not used, since with the first format change we can deal implicitly.
if [ -f "${HISTORY_FILE}.properties" ]; then
source "${HISTORY_FILE}.properties" 2>/dev/null
else
event_index_version=0
fi
log "\t\t+- Reading $HISTORY_FILE (v$event_index_version) with $(wc -l $HISTORY_FILE | cut -d ' ' -f1) events ..."
# for unavailable timestamps (upgrading) put the timestamp 2 days in the future
# a subsequent run from pr-auto-timer, will either correct this or leave it;
# if it remains, than the entry is considered old after two days..
new_timestamp=$(($(date +%s)+2*24*60*60))
while read line; do
# ignore empty lines
if [ -z "$line" ]; then
log "\t\t+- Removing empty line from index."
continue
fi
# now split it in it's components
begin_ifs_block
IFS='|'
set -- $line
title=$1
info=$2
timestamp=$3
filename=$4
end_ifs_block
# now check, if some fields are empty and provide some initial values for them
# this can happen with manual entries and when upgrading from an older file format
# in case we anyhow generate a new index, we also reset the timestamp - this way we
# can also get updated timestamps if the modification date changes
if [ -z "$timestamp" ] || ( [ $AUTOGEN_SHOW_HISTORY -eq 1 ] && [ -f "$filename" ] ); then
#log "\t\t+- Upgrading '$line' with new timestamp '$new_timestamp'."
timestamp=$((new_timestamp++))
fi
# if there is no filename yet, we set some placeholder
# we also remove/reset the filename, in case we are going to regenerate the index anyhow
# the given placeholder ensures, that a new found path always 'wins' in sort against the placeholder
if [ -z "$filename" ] || [ $AUTOGEN_SHOW_HISTORY -eq 1 ]; then
#log "\t\t+- Adding placeholder filename to '$line'".
filename="ZZZZ-not-available-ZZZZ"
fi
#write the cleaned/upgraded line to the temporary index
echo -e "$title|$info|$timestamp|$filename|unverified-magic-MYmtmIQMsoQ=" >> "$HISTORY_IDX_TMP_FILE"
done < "$HISTORY_FILE"
else
log "\t\t+- $HISTORY_FILE does not yet exist. Starting with empty index."
touch "$HISTORY_IDX_TMP_FILE"
fi
}
generate_history_index() {
# Generates/updates the $ME.show_history-file from your recordings
# No parameters
# Result: merged $ME.show_history with previous values and available records
local rec_path info info1 info2 title show history_index ts_file timestamp magic_token broken_records broken_records_count recorded_shows
log "\t\tgenerate_history_index: collecting information from previously recorded events ..."
read_history_index
history_index="$(cat $HISTORY_IDX_TMP_FILE)\n"
# shall we auto-generate the history index?
if [ $AUTOGEN_SHOW_HISTORY -eq 1 ]; then
# now iterate over all provided paths
begin_ifs_block
IFS=';'
for rec_path in ${MYRECORDPATHS//"std::neutrino"/$neutrino_rec_dir}
do
if [ $BROKEN_RECORD_HANDLING -gt 0 ]; then
log "\t\t+- Searching path '$rec_path' recursively for broken *.ts-files (file size smaller than ${BROKEN_FILE_SIZE_LIMIT}kB)."
broken_records=$(find "$rec_path" -name '*.ts' -size -${BROKEN_FILE_SIZE_LIMIT}k)
broken_records_count=$(echo -e "$broken_records" | sed '/^$/d' | wc -l)
if [ $broken_records_count -gt 0 ]; then
if [ $BROKEN_RECORD_HANDLING -eq 2 ]; then
if [ $DEBUG_DRY_RUN -eq 0 ]; then
log "\t\t| \\- Removing $broken_records_count broken record(s) (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
echo -e "$broken_records" | xargs rm 2>/dev/null
else
log "\t\t| \\- Would remove $broken_records_count broken records now (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING). Skipped due to DEBUG-DRY-RUN."
fi
# In this case, we will detect the a missing transport stream and handle the xml with the next part accordingly.
# To avoid double work, we therefore reset BROKEN_RECORDS=""
broken_records=""
else
log "\t\t| \\- Found $broken_records_count broken record(s), but leaving files untouched (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
fi
else
log "\t\t| \\- No broken records found."
fi
fi
log "\t\t+- Searching path '$rec_path' recursively for *.xml-files"
recorded_shows=$(find "$rec_path" -name '*.xml')
IFS=$'\n'
for show in $recorded_shows
do
# Note: info2 is only extracted until the first newline. This is probably ok, since we need anyhow only the
# first $INFO2_CHARACTERS (30) characters from it iff info1 is empty.
#echo "Processing '$show' .."
title=$(grep epgtitle "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s/<epgtitle>//g;s/<\/epgtitle>//g' | html2text)
info1=$(grep info1 "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s/<info1>//g;s/<\/info1>//g' | html2text)
info2=$(grep "<info2>" "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s/<info2>//g;s/<\/info2>//g' | html2text)
# now check which information is available in the stored xml
if [ -z "$info1" ] || [ "$info1" == "$title" ]; then
# info1 was empty or exactly the same as the title
# in this case, we use the $INFO2_CHARACTERS from info2; nothing else to do; if this is empty too, we can't do anything
info=${info2:0:$INFO2_CHARACTERS}
else
info="$info1"
fi
# create a magic token for good files. It is important, that this token is alphabetically BEFORE the bad-token
# and also the unverified token (therefore it starts with 'a' - the random string is just to make it unique, so we can grep later for it
# note that the random string used when reading the old HISTORY_FILE must be the same as this one here.
magic_token="alright-magic-MYmtmIQMsoQ="
# finally check if the transport stream exists and if it bigger than a given size
ts_file=${show%".xml"}".ts"
if ( [ -f "$ts_file" ] && ([ $BROKEN_RECORD_HANDLING -eq 0 ] || [ "$(echo -e "$broken_records" | grep "$ts_file")" == "" ]) ); then
# all good, get the last modification time of the .ts-file as a timestamp
timestamp=$(date -r "$ts_file" +%s)
#file_size_kb=`du -k "$filename" | cut -f1`
#echo "$ts: $timestamp - $(date -r "$show" +%s)"
else
# No transport stream (recording) found. In this case we must handle a broken record if activated
timestamp=$(date -r "$show" +%s)
case $BROKEN_RECORD_HANDLING in
0)
log "\t\t| +- Warning: No or broken transport stream for $show found (Title='$title', Info='$info'). Still adding it to history index (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
;;
1)
log "\t\t| +- Detected missing or broken transport stream for $show (Title='$title', Info='$info'). Removing from history index (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
# make sure, this comes alphanumerically after the "good" token - with the later unique sort this should "survive" against the good one.
magic_token="bad-magic-5NZm8PpjT8w="
;;
2)
if [ $DEBUG_DRY_RUN -eq 0 ]; then
log "\t\t| +- Detected missing or broken transport stream for $show (Title='$title', Info='$info'). Removing from history index and deleting. (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING)."
rm -f "$show" 2>/dev/null
else
log "\t\t| +- Detected missing or broken transport stream for $show (Title='$title', Info='$info'). Would now remove it from history index and delete it. (BROKEN_RECORD_HANDLING=$BROKEN_RECORD_HANDLING). Skipping the real operation due to DEBUG_DRY_RUN=$DEBUG_DRY_RUN."
fi
magic_token="bad-magic-5NZm8PpjT8w="
;;
*)
log "\t\t| +- ERROR: This should not happen - $BROKEN_RECORD_HANDLING is no valid value BROKEN_RECORD_HANDLING ($show: Title='$title', Info='$info')."
continue
;;
esac
fi
# add to the index iff there are meaningful values available.
if [ -n "$title" ] && [ "$title" != "not available" ] && [ -n "$info" ]; then
history_index="$history_index$title|$info|$timestamp|$ts_file|$magic_token\n"
else
log "\t\t| +- Skipping file $show ($(date -D %s -d $timestamp +'%Y-%m-%d %H:%M:%S')) due to invalid info: Title='$title', Info='$info'"
fi
done
done
end_ifs_block
fi
# at this point HISTORY_INDEX contains all values from the old files and
# also a value for all collected files from the recorded directories (if active)
# Let's sort it and remove all duplicates, ignoring timestamps and ignoring bad records
history_index=$(echo -ne "$history_index" | sort -t'|' -u -k1,2 | grep "MYmtmIQMsoQ=")
# write it to a temp file, so we can access it from different subprocesses
echo -e "$history_index" > $HISTORY_IDX_TMP_FILE
}
clean_history_file() {
# Write (potentially updated) history index of recorded shows to the index file
# Clean the file by removing n entries, so that the maximum entries given in MAX_HISTORY_ENTRIES is not violated
# Only entries, to which no recorded file exists will be removed - otherwise they would be added over and over again
# A setting of MAX_HISTORY_ENTRIES=0 deactivates the size check. If there are more physically available files, than
# entries allowed, the MAX_HISTORY_ENTRIES will be ignored and a warning is issued in the logfile.
# No parameter
local entries line title info timestamp filename nr_removed_entries=0 new_timestamp
# proceed only if there is a tempfile, otherwise there is nothing to do here
if [ -e "$HISTORY_IDX_TMP_FILE" ]; then
# check if we are in debug-dry-run. If yes, just skip any processing of the file. Otherwise proceed by writing
# a new history index
if [ $DEBUG_DRY_RUN -eq 0 ]; then
entries=$(wc -l $HISTORY_IDX_TMP_FILE | cut -d ' ' -f1)
# calculate the number of lines which need to be removed
if [ $MAX_HISTORY_ENTRIES -gt 0 ] && [ $entries -gt $MAX_HISTORY_ENTRIES ]; then
over_limit=$((entries-MAX_HISTORY_ENTRIES))
log "\tTrying to minimize generated history index with $entries entries by $over_limit entries before writing to file $HISTORY_FILE .."
else
over_limit=0
log "\tCleaning generated history index with $entries entries and writing to file $HISTORY_FILE .."
fi
# create also the temporary file for storing the number of removed elements
echo 0 > "${HISTORY_IDX_TMP_FILE}_nr_removed"
# try to minimize the file by removing old entries
sort -t '|' -k3 "$HISTORY_IDX_TMP_FILE" | while read line; do
# ignore empty lines
if [ -z "$line" ]; then
echo $((++nr_removed_entries)) > "${HISTORY_IDX_TMP_FILE}_nr_removed"
log "\t+- Removing empty line from index."
continue
fi
begin_ifs_block
IFS='|'
set -- $line
title=$1
info=$2
timestamp=$3
filename=$4
end_ifs_block
if [ $nr_removed_entries -ge $over_limit ] || [ -f "$filename" ]; then
# not oversize or enough entries removed or filename exists
# insert this line also into the new file, and use the current format
echo -e "$title|$info|$timestamp|$filename" >> "${HISTORY_IDX_TMP_FILE}2"
else
# not enough events removed and the file does not exist anymore
# hence this entry will be removed -- here we just skip it
echo $((++nr_removed_entries)) > "${HISTORY_IDX_TMP_FILE}_nr_removed"
log "\t+- Removing '$title' ('$info') with timestamp $timestamp ($(date -D %s -d $timestamp +'%Y-%m-%d %H:%M:%S')) from index."
fi
done
# grab the number from the file, since the subprocess from read does not allow variables to pass back
nr_removed_entries=$(cat "${HISTORY_IDX_TMP_FILE}_nr_removed")
if [ $nr_removed_entries -lt $over_limit ]; then
#we could not remove enough. print a warning in the log
log "\tWARNING: Removed only $nr_removed_entries / $over_limit entries entries from index. Increase MAX_HISTORY_ENTRIES (=$MAX_HISTORY_ENTRIES) or delete some old files!"
else
# everything is fine. minimizing worked
log "\tCleaned the generated history index by removing $nr_removed_entries entries. (required $over_limit entries)"
fi
# just write the new index again sorted by column 1 and 2
sort -t '|' -u -k 1,2 "${HISTORY_IDX_TMP_FILE}2" > "$HISTORY_FILE"
entries=$(cat $HISTORY_FILE | wc -l)
log "\tWrote the updated history index with $entries entries to file $HISTORY_FILE."
# write also the meta-information, for now only the version of the file
prop_file="${HISTORY_FILE}.properties"
echo -e "# This file was automatically generated by pr-auto-timer v$VERSION" > $prop_file
echo -e "# Do not delete or change this file manually. It contains properties" >> $prop_file
echo -e "# about the .show_history file which are used by pr-auto-timer to" >> $prop_file
echo -e "# determine the correct format of the file." >> $prop_file
echo -e "event_index_version=1" >> $prop_file
else
log "\tDEBUG DRY RUN: Would update now $HISTORY_FILE now. Skipped due to dry run option."
fi
fi
}
generate_timer_index() {
# Retrieves all timers, collects EPG-information for each one and generates an index with this information
# No parameters
# Result: The temporary file TIMER_IDX_TMP_FILE contains info all currently set timers.
# Currently there is a single line of the format "Title|Info" for each timer.
local timerlist timer_id timer_type timer_start channel_name epg_data title info1 info2 info timer_index
log "\t\tgenerate_timer_index: collecting information from already set timers ..."
# first retrieve all set timers
timerlist=$(wget -qO - "$webserver_url/control/timer")
# process each timer, and try to find EPG-data for it
timer_index=$(echo -e "$timerlist" | while read timer;
do
channel_name=$(echo "$timer" | cut -d ' ' -f 8-)
timer_id=$(echo "$timer" | cut -d ' ' -f 1)
timer_type=$(echo "$timer" | cut -d ' ' -f 2)
timer_start=$(echo "$timer" | cut -d ' ' -f 6)
# We care only for record-type timers
if [ $timer_type -eq 5 ]; then
# get EPG information for each of them
log "\t\t+- Retrieving EPG-data for timer id $timer_id starting at $(date -D %s -d $timer_start +'%Y-%m-%d %H:%M:%S') ($timer_start) on channel $channel_name ..."
epg_data=$(find_show_by_start_sec "$channel_name" "$timer_start")
if [ -n "$epg_data" ]; then
begin_ifs_block
IFS='|'
set -- $epg_data
title=$5
info1=$6
info2=$7
end_ifs_block
# now check which information is available in the stored xml
if [ -z "$info1" ] || [ "$info1" == "$title" ]; then
# info1 was empty or exactly the same as the title
# in this case, we use the INFO2_CHARACTERS from info2 nothing else to do; if this is empty too, nothing can be done
info=${info2:0:$INFO2_CHARACTERS}
else
info="$info1"
fi
log "\t\tAdding '$title|$info' to the future timer index."
# update the index
echo -e "$title|$info\n"
else
log "\t\t| +- Warning: No EPG info found for timer id $timer_id starting at $timer_start on channel $channel_name. Skipping"
fi
fi
done;)
# For the sake of completeness, just sort it - there should not be any duplicates here, but to be sure
# (could only happen if a timer was set additionally)
timer_index=$(echo -e "$timer_index" | sort -u)
# write it to a temporary file, in order to access from different subprocesses
echo -e "$timer_index" > $TIMER_IDX_TMP_FILE
}
add_historic_timer() {
#Timer zur internen Liste hinzufügen, um nochmaligen Eintrag nach manuellem löschen zu verhindern.
#$1: channel name
#$2: alarm time
#$3: stop time
#$4: timerd event type
local S
S="$1;$2;$3;$4"
if [ $DEL_TIMER_REFRESH -lt 2 ] && [ "$TMR2_FILE" != "" ]; then
if ! find_historic_timer "$1" $2 $3 $4 1; then
echo -e "$S" >> $TMR2_FILE
fi
fi
}
clean_historic_timer_file() {
local jetzt=$(date +%s)
local i=0
local a=0
if [ -e $TMR2_TEMPFILE ]; then
rm $TMR2_TEMPFILE
log "\tFound old tempfile for historic timers and deleted it."
fi
if [ $DEL_TIMER_REFRESH -lt 2 ]; then
log "\tProcessing historic timers index (DEL_TIMER_REFRESH=$DEL_TIMER_REFRESH).."
if [ -e $TMR2_FILE ]; then
while read line
do
# split line
begin_ifs_block
IFS=';'
set -- $line
channelname="$1"
starttime=$2
endtime=$3
type=$4
end_ifs_block
if [ $endtime -gt $jetzt ]; then
if [ $MAX_TMR2_COUNT -gt $a ]; then
echo -e "$line" >> $TMR2_TEMPFILE
fi
a=$(($a+1))
else
i=1
fi
done < $TMR2_FILE
if [ $i -eq 1 ]; then
log "\t$TMR2_FILE cleaned (removed $a entries)."
rm $TMR2_FILE
if [ -e $TMR2_TEMPFILE ]; then
cp $TMR2_TEMPFILE $TMR2_FILE
fi
fi
fi
if [ -e $TMR2_TEMPFILE ]; then
rm $TMR2_TEMPFILE
log "\tDeleted tempfile for historic timers."
fi
else
if [ -e $TMR2_FILE ]; then
rm $TMR2_FILE
log "\tFound historic timer file, but historic timers are deactivated in settings (DEL_TIMER_REFRESH=2). Deleted $TMR2_FILE."
fi
fi
}
find_historic_timer() {
#Find an (exact) existing timer using channel name, alarm time, stop time and event type
#$1: channel name
#$2: alarm time
#$3: stop time
#$4: event type (5: record, 3: zapto)
#$5: Override = 1
local S
local ret
S="$1;$2;$3;$4"
ret=1
if [ $DEL_TIMER_REFRESH -eq 0 ] || [ $5 -eq 1 ]; then
if [ -e $TMR2_FILE ]; then
while read line
do
if [ "$line" == "$S" ]; then
ret=0
break
fi
done < $TMR2_FILE
fi
fi
return $ret
}
find_already_recorded() {
# Find if a show with a certain description and info is either already recorded (i.e. in the history-file) or
# a timer was already set (i.e. it is in the future timer index)
#$1: description
#$2: info
local show ret title info timestamp
show="$1|$2"
ret=1
# determine if the the HISTORY_INDEX needs updating
if [ ! -e "$HISTORY_IDX_TMP_FILE" ]; then
log "\t\tfind_already_recorded: Updating/generating index file for previous records..."
generate_history_index
fi
# determine if the TIMER_INDEX needs updating
if [ ! -e "$TIMER_IDX_TMP_FILE" ]; then
log "\t\tfind_already_recorded: Updating future timer index ..."
generate_timer_index
fi
if [ -e $HISTORY_IDX_TMP_FILE ]; then
while read line
do
#echo -e "processing line $line .."
begin_ifs_block
IFS='|'
set -- $line
title=$1
info=$2
end_ifs_block
if [ "$title|$info" == "$show" ]; then
ret=0
break
fi
done < $HISTORY_IDX_TMP_FILE
fi
if [ -e $TIMER_IDX_TMP_FILE ]; then
while read line
do
#echo -e "processing line $line .."
if [ "$line" == "$show" ]; then
ret=0
break
fi
done < $TIMER_IDX_TMP_FILE
fi
return $ret
}
#END SECTION "Event & Timer Index"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Timer Handling"
add_timer() {
#Add a timer using channel name, channel id, alarm time, stop time and event type if it does not already exist. Overlapping timers are removed before.
#$1: channel name
#$2: channel id
#$3: alarm time
#$4: stop time
#$5: timerd event type
#$6: alt rec dir
#Note: Announce time is ignored.
local S ret
S="$1;$3;$4;$5"
ret=1
if [ "$first_match" = "done" ]; then
return $ret
fi
if find_neutrino_timer "$1" $3 $4 $5; then
log "\t\tadd_timer: --- An identical timer '$1' $3 $4 $5 is currently already set. Skipping. ---"
elif [ "$ignore_timer_refresh" == "false" ] && find_historic_timer "$1" $3 $4 $5 0; then
log "\t\tadd_timer: --- An identical timer '$1' $3 $4 $5 was previously set. Skipping. ---"
elif prevent_timer_by_timespan $3; then
log "\t\tadd_timer: --- Preventing timer '$1' $3 ($(date -D %s -d ${3} +"%H:%M")) by timespan ($timespan). Skipping. ---"
else
remove_overlapping_timer "$1" $3 $5
if [ $DEBUG_DRY_RUN == 0 ]; then
result=$(wget -qO - "$webserver_url/control/timer?action=new&type=$5&alarm=$3&stop=$4&channel_id=$2&rec_dir=$6")
if [ "$ignore_timer_refresh" == "false" ]; then
add_historic_timer "$1" $3 $4 $5
ret=0
fi
else
result="DISABLED (DEBUG_DRY_RUN)"
fi
log "\t\tadd_timer: Adding timer '$1' $3 $4 $5: $result."
if [ "$first_match" = "true" ]; then
log "\t\tadd_timer: Ignoring possible following shows on '$1'."
first_match=done;
fi
fi
return $ret
}
remove_overlapping_timer() {
#Find and remove overlapping/similar timers resulting from changed EPG data
#$1: channel name (ignore case)
#$2: alarm time
#$3: timerd event type
#returns: API result (hopefully: 'ok')
#Note: remove_overlapping_timer ignores repeated timers
alarm_time=$2
wget -qO - "$webserver_url/control/timer" | awk -v pattern="# $3 0 0 # # # $1" '{
timer_id=$1;
tstart_sec=$6;
$1=$5=$6=$7="#";
if(tolower($0)==tolower(pattern)) {
print timer_id " " tstart_sec;
}
}' | while read timer; do
begin_ifs_block
IFS=' '
set -- $timer
timer_id=$1
tstart_sec=$2
end_ifs_block
if [ $alarm_time -gt $tstart_sec ]; then
diff_secs=$(( $alarm_time - $tstart_sec ))
else
diff_secs=$(( $tstart_sec - $alarm_time ))
fi
if [ $diff_secs -le $max_diff_secs ]; then
if [ $DEBUG_DRY_RUN == 0 ]; then
result=$(wget -qO - "$webserver_url/control/timer?action=remove&id=$timer_id")
else
result="DISABLED: DEBUG_DRY_RUN"
fi
log "\t\tremove_overlapping_timer: Timer id: $timer_id overlaps by $diff_secs seconds (max: $max_diff_secs). Removing: $result"
fi
done
}
find_neutrino_timer() {
#Find an (exact) existing timer using channel name, alarm time, stop time and event type
#$1: channel name (ignore case)
#$2: alarm time
#$3: stop time
#$4: event type (5: record, 3: zapto)
#Note: find_neutrino_timer ignores repeated timers
wget -qO - "$webserver_url/control/timer" | awk -v pattern="# $4 0 0 # $2 $3 $1" '
BEGIN {
returncode=1;
}
{
$1=$5="#";
if(tolower($0)==tolower(pattern)) {
returncode=0;
}
}
END {
exit returncode;
}
'
}
get_overlapping_timers() {
# Retrieve the maximum number of concurrent/overlapping timers for a given time span
# $1: alarm time
# $2: stop time
# $3: channel_id for the new timer
# Returns a value of the form "max_transponders|max_recordings", where max_transponders is the number of how many tuners
# would be required, when setting the new timer and max_recordings is the number of how many recordings would happen
# at most in parallel during the timespan of the new timer
local result timerlist timerlist_extended maxts maxrec
# get the timerlist; Note: this cannot be cached, otherwise we would miss timers set during the same run
# if we really need to cache it, we would need to update the cache as well, when adding a new timer
timerlist=$(wget -qO - "$webserver_url/control/timer")
# we need the transponder_id inside awk, to determine how many tuners are really needed
# therefore, the retrieved timerlist is extended to include this information before passing
# it to awk.
timerlist_extended=$(echo -e "$timerlist" | while read timer; do
channel_name=$(echo "$timer" | cut -d ' ' -f 8-)
timer_line=$(echo "$timer" | cut -d ' ' -f 1-7)
sid=$(get_channel_id_by_channel_name "$channel_name")
echo "$timer_line $sid $channel_name"
done;)
result=$(echo -e "$timerlist_extended" | awk -v span_start="$1" -v span_stop="$2" -v span_sid="$3" '
BEGIN {
#printf ("Analysing timespan %i-%i (%is = %i Min) with respect to new timer on sid %s\n",span_start,span_stop,span_stop-span_start,(span_stop-span_start)/60,span_sid);
span_tsid=substr(span_sid,5,4);
#initalize the array with the tsid of the potential new timer
for (time_point=span_start; time_point<=span_stop; time_point++) {
time_span[time_point,"ts"] = span_tsid;
time_span[time_point,"rec"] = 1;
}
}
{
timer_type=$2;
# process timers only for record timers
if (timer_type == 5) {
# check for each existing timer, if it overlaps with the provided timespan
timer_start=$6;
timer_stop=$7;
timer_sid=$8;
timer_tsid=substr(timer_sid,5,4)
timer_channel_name=substr($0, index($0,$9));
# uncomment for debugging, keep commented in normal usage, since the output is the result, where a single integer ist expected
#printf ("%s: alarmTime=%i, stopTime=%i, Type=%i, channel=%s (CID: %s, TSID: %s)\n", $0,timer_start,timer_stop,timer_type,timer_channel_name,timer_sid, timer_tsid);
# create an array with an entry for each second of the given time span with the number of concurrent timers
# wondering, if there is a more efficient way to do this.. ?
for (time_point=span_start; time_point<=span_stop; time_point++) {
# verify if timepoint is within the currently processed timer
if ((time_point >= timer_start) && (time_point <= timer_stop)) {
# this timer clashes with the new timer. Check if the tsid is already used, and if not add the tsid
if (! match(time_span[time_point,"ts"],timer_tsid)) {
time_span[time_point,"ts"] = time_span[time_point,"ts"] "|" timer_tsid;
}
# increase the number of recordings for this second
time_span[time_point,"rec"]++;
}
}
}
}
END {
#printf "done - calculating max tuners required.. \n";
max_transponders=0;
max_recordings=0;
for (i=span_start;i<=span_stop;i++) {
different_transponders=split(time_span[i,"ts"],array,"|")
#printf time_span[i,"ts"] ";";
#printf different_transponders;
if (different_transponders > max_transponders) {
max_transponders=different_transponders;
}
if (time_span[i,"rec"] > max_recordings) {
max_recordings=time_span[i,"rec"];
}
}
printf ("%i|%i", max_transponders,max_recordings);
exit 0
}
')
if [ $? -eq 0 ]; then
#maxts=$(echo $result | cut -d '|' -f1)
#maxrec=$(echo $result | cut -d '|' -f2)
#log "\t\tget_overlapping_timers: Would require $maxts tuners (for $maxrec parallel recordings) to set new timer in timespan '$1-$2'. (Max available tuners: $MAX_TUNERS)"
echo -e "$result"
return 0
fi
}
prevent_timer_by_timespan() {
#$1: alarm time
local ret
if [ "$timespan" = "*" ]; then
return 1 # don't prevent
fi
timer_start_h=$(($(echo $(date -D %s -d ${1} +%H) | sed 's/^0//') * 60 * 60))
timer_start_m=$(($(echo $(date -D %s -d ${1} +%M) | sed 's/^0//') * 60))
timer_start=$((${timer_start_h} + ${timer_start_m}))
timespan_from_h=$(($(echo ${timespan_start_h} | sed 's/^0//') * 60 * 60))
timespan_from_m=$(($(echo ${timespan_start_m} | sed 's/^0//') * 60))
timespan_from=$((${timespan_from_h} + ${timespan_from_m}))
timespan_till_h=$(($(echo ${timespan_stop_h} | sed 's/^0//') * 60 * 60))
timespan_till_m=$(($(echo ${timespan_stop_m} | sed 's/^0//') * 60))
timespan_till=$((${timespan_till_h} + ${timespan_till_m}))
ret=1
if [ "$timespan_over_midnight" = "false" ]; then
if ! [ $timer_start -ge $timespan_from -a $timer_start -le $timespan_till ]; then
# timer doesn't start inside timespan
ret=0 # prevent
fi
else
if [ $timer_start -gt $timespan_till -a $timer_start -lt $timespan_from ]; then
# timer starts outside timespan
ret=0 # prevent
fi
fi
return $ret
}
#END SECTION "Timer Handling"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Sky Cinema"
get_sky_moviedata() {
# we use a static tmp-dir
test -d /tmp/$ME || mkdir -p /tmp/$ME
data_file=/tmp/$ME/sky_moviedata
rm -f $data_file # force download; debug
if [ -e $data_file ]; then
if [ $(($(date +%s)\-$(date -r $data_file +%s))) -gt 86400 ]; then
rm -f $data_file
fi
fi
if [ ! -e $data_file ]; then
log "\t\tget_sky_moviedata: get informations ..."
wget -qO "$data_file" "http://www.moviepilot.de/liste/sky-neustarts-filmschauer"
fi
test -e $data_file && cat $data_file | awk '
BEGIN {
false=0;
found=false;
}
END {
}
/board_item_description/ {
found="date";
date="";
next;
}
/clearfix/ {
found=false;
next;
}
/<h3>/ {
found="movie";
movie="";
next;
}
/<\/h3>/ {
found=false;
next;
}
/<p>|\/serie\// {
next;
}
// {
if ( found == "date" ) {
gsub (/(\,|\.|\:|\|)/, " "); # replace ",.:|" with " "
gsub ("Januar" , "1");
gsub ("Februar" , "2");
gsub ("März" , "3");
gsub ("April" , "4");
gsub ("Mai" , "5");
gsub ("Juni" , "6");
gsub ("Juli" , "7");
gsub ("August" , "8");
gsub ("September", "9");
gsub ("Oktober" , "10");
gsub ("November" , "11");
gsub ("Dezember" , "12");
# YYMMDDhhmm
#date = 9912312313
date = $6$5$4$7$8;
}
if ( found == "movie" ) {
gsub (/^[[:space:]]*/, "");
gsub (/&#x27;/, "'\''");
movie = gensub(/<\/?[^>]+>/, "", "g");
printf("%s %s\n", date, movie);
}
next;
}
// {
found=false;
next;
}
'
}
get_sky_cinema_channel() {
#$1: include search words (ignore case)
#$2: exclude search words (ignore case)
#returns: name of the first(!) matching channel
# replace ' ' with '.*' and append '.*'
include="${1// /.*}.*"
# replace ' ' with '|'
exclude="${2// /|}"
# replace '+' with '\+'
include="${include//+/\\+}"
exclude="${exclude//+/\\+}"
# tolower
include=$(echo $include | awk '{print tolower($0)}')
exclude=$(echo $exclude | awk '{print tolower($0)}')
temp_file="$temp_dir/channellist"
if [ ! -e "$temp_file" ]; then
wget -qO "$temp_file" "$webserver_url/control/channellist"
fi
sky_cinema_channels=$(grep -iE "Sky.*Cinema.*" $temp_file | cut -d" " -f2- | sort -u )
echo "$sky_cinema_channels" | \
while read channel; do
echo "$channel" | awk '{print tolower($0)}' | grep -qE "($include)"
if [ $? -ne 0 ]; then
continue
fi
echo "$channel" | awk '{print tolower($0)}' | grep -qvE "($exclude)"
if [ $? -eq 0 ]; then
echo "$channel"
break
fi
done
}
parse_sky_cinema() {
if [ "$SKY_CINEMA" = "" ]; then
sky_cinema=""
return
fi
# split $SKY_CINEMA
begin_ifs_block
IFS=';'
set -- $SKY_CINEMA
sky_cinema=$1
dowfilter=${2:-*}
recdir=$3
end_ifs_block
# split dowfilter
begin_ifs_block
IFS=','
set -- $dowfilter
dowfilter=$1
timespan=$2
end_ifs_block
sky_start_sec_add=0
sky_exclude_channels="Active Action Comedy Emotion Family Hits Nostalgie"
case $(echo $sky_cinema | awk '{print toupper($0)}') in
SD)
sky_cinema=$(get_sky_cinema_channel "Sky Cinema" "+1 +24 HD $sky_exclude_channels")
;;
HD)
sky_cinema=$(get_sky_cinema_channel "Sky Cinema HD" "+1 +24 $sky_exclude_channels")
;;
1)
sky_cinema=$(get_sky_cinema_channel "Sky Cinema +1" "+24 HD $sky_exclude_channels")
sky_start_sec_add=$((60 \* 60))
;;
1HD)
sky_cinema=$(get_sky_cinema_channel "Sky Cinema +1 HD" "+24 $sky_exclude_channels")
sky_start_sec_add=$((60 \* 60))
;;
24)
sky_cinema=$(get_sky_cinema_channel "Sky Cinema +24" "+1 HD $sky_exclude_channels")
sky_start_sec_add=$((60 \* 60 \* 24))
;;
24HD)
sky_cinema=$(get_sky_cinema_channel "Sky Cinema +24 HD" "+1 $sky_exclude_channels")
sky_start_sec_add=$((60 \* 60 \* 24))
;;
*)
sky_cinema=""
;;
esac
}
#END SECTION "Sky Cinema"
#######################################################################################
#######################################################################################
#BEGIN SECTION "Main"
# set the signal handler
trap signal_handler INT TERM
# First initialize the values from the config, otherwise we cannot log anywhere
init_config
init_temp
HISTORY_IDX_TMP_FILE="$temp_dir/history_index"
TIMER_IDX_TMP_FILE="$temp_dir/timer_index"
# Now get command line option, as these might override some values from the config or default variables
parse_options $@
if [ -e $PID_FILE ]; then
log "$ME ist already running. Exiting..."
exit $EXIT_ALREADY_RUNNING
else
echo $$ > $PID_FILE
fi
# We happily started..
log ""
log "$ME V$VERSION started."
if [ $DEBUG_TEMP == 1 ]; then
log "\tmain: !!! WARNING: DEBUG_TEMP is enabled. Temporary files will not be removed !!!"
fi
if [ $DEBUG_DRY_RUN == 1 ]; then
log "\tmain: !!! WARNING: DEBUG_DRY_RUN is enabled. Timers will not be modified !!!"
fi
# prepare for main operation
init_settings
init_webserver
init_displaylog
# Perform pre-actions, if activated.
log "Executing configured pre-actions ... "
if [ -n "$PRE_ACTION" ]; then
log "\tExecuting '$PRE_ACTION'"
eval $PRE_ACTION
log "\tDone."
else
log "\tNo pre-action configured."
fi
# check for radio/tv mode of the box and switch to tv, in case radio was found
# todo: we should also give some feedback on the screen, in case -m is used.
box_mode=$(wget -q -O - $webserver_url/control/getmode?channelsmode=true | dos2unix)
case $box_mode in
tv)
log "\tmain: Box is in ${box_mode}-mode. Proceeding without changing mode."
;;
radio)
box_standby=$(wget -q -O - $webserver_url/control/standby | dos2unix)
log "\tmain: Box is in ${box_mode}-mode (standby=$box_standby). Trying to switch to tv-mode ..."
result=$(wget -q -O - $webserver_url/control/setmode?tv | dos2unix)
if [ "$result" == "ok" ]; then
log "\tmain: Successfully switched to tv-mode. Will switch back to $box_mode when finished."
else
log "\tmain: Could not switch to tv-mode. This is bad and adding timers will not work. Exiting now."
cleanup
exit 0
fi
;;
*)
log "\tmain: Box is in ${box_mode}-mode! This should not happen and is considered a bad thing. Exiting now."
cleanup
exit 0
;;
esac
if [ $opt_menu ]; then
msgbox title="$NAME" size=20 timeout=5 refresh=0 cyclic=0 popup="Running..." >/dev/null
fi
for rule_file in $RULE_FILES; do
log "\tmain: processing '${rule_file}'"
rule_line=0
cat $rule_file | while read line ;do
rule_line=$((${rule_line}+1))
if echo $line | egrep -q '^[[:space:]]*([^#;]+);+([^;]+);+([^;]+);?([^;/]+)?;?([^;]+)?;?$'; then
log ""
log "\tmain: Processing rule: $line"
# split config line
begin_ifs_block
IFS=';'
set -- $line
channelname=$1
dowfilter=$2
regex=$3
flags=$4
recdir=$5
end_ifs_block
# split dowfilter
begin_ifs_block
IFS=','
set -- $dowfilter
dowfilter=$1
timespan=$2
end_ifs_block
# split regex
begin_ifs_block
IFS=','
set -- $regex
regex=$1
xregex=$2
end_ifs_block
parse_regexes
parse_timespan "$timespan"
parse_flags "$flags"
parse_recdir
log "\tmain: channel: '$channelname', dow: '$dowfilter', timespan: '$timespan', regex: '$regex', extended regex: '${xregex:-none}', extended regex option: '${xregex_option:-none}', event type: '$event_type', flags: '${flags:-none}', rec dir: '${recdir:-std::neutrino}'"
shows=$(find_show "$channelname" "$regex" "$xregex" "$xregex_option")
shows_found=0
matches=$(echo "$shows"|wc -l)
echo -e "$shows" | {
while read result; do
if [ "$result" = "" ]; then
break
fi
shows_found=$(( $shows_found + 1 ))
log "\tmain: Processing match $shows_found / $matches ..."
begin_ifs_block
IFS='|'
set -- $result
channelid=$1
start_sec=$2
stop_sec=$3
channelname=$4
description=$5
info1=$6
info2=$7
end_ifs_block
set -- $(get_corrected_start_stop_times $start_sec $stop_sec $event_type)
start_sec_corrected=$1
stop_sec_corrected=$2
dow=$(date -D %s -d ${start_sec} +%a)
parse_dowgroup
# check which information was send in the EPG
if [ -z "$info1" ] || [ "$info1" == "$description" ]; then
# info1 was empty or exactly the same as the title
# in this case, we use the $INFO2_CHARACTERS from info2; nothing else to do; if this is empty too, we can't do anything
info=${info2:0:$INFO2_CHARACTERS}
else
info="$info1"
fi
log "\t\tEvent content: '$description' ('$info') on channel '$channelname' ($channelid)"
log "\t\tEvent times: $(date -D %s -d ${start_sec} +'%a, %Y-%m-%d'), $(date -D %s -d ${start_sec} +'%H:%M:%S')-$(date -D %s -d ${stop_sec} +'%H:%M:%S') ($start_sec-$stop_sec), Corrected: $(date -D %s -d ${start_sec_corrected} +'%H:%M:%S')-$(date -D %s -d ${stop_sec_corrected} +'%H:%M:%S') ($start_sec_corrected-$stop_sec_corrected)"
if [ $start_sec_corrected -gt $(date +%s) ]; then
if [ "$dow" = "$dowfilter" -o "$dowfilter" = "*" -o "$dowgroup" = "true" ]; then
if [ "$prevent_duplicates" == "false" ] || ! find_already_recorded "$description" "$info" "$info2"; then
overlaps=$(get_overlapping_timers $start_sec_corrected $stop_sec_corrected $channelid)
maxts=$(echo $overlaps | cut -d '|' -f1)
maxrec=$(echo $overlaps | cut -d '|' -f2)
log "\t\tmain: Would require $maxts / $MAX_TUNERS tuners (for $maxrec / $MAX_RECORDS parallel recordings) to set new timer in timespan '$(date -D %s -d ${start_sec_corrected} +'%H:%M:%S')-$(date -D %s -d ${stop_sec_corrected} +'%H:%M:%S')'."
if [ $maxts -le $MAX_TUNERS ] && [ $maxrec -le $MAX_RECORDS ]; then
if add_timer "$channelname" $channelid $start_sec_corrected $stop_sec_corrected $event_type $recdir; then
log "\t\tmain: ########### Timer for '$description' ('$info') on channel '$channelname' successfully added ###########!"
if [ "$prevent_duplicates" == "true" ] && ( [ -n "$info" ] || [ "$allow_empty_info" == "true" ] ); then
log "\t\tmain: Duplicate prevention is active; adding '$description|$info' to the future timer index."
echo "$description|$info" >> $TIMER_IDX_TMP_FILE
fi
#else
# log "\t\tmain: Timer NOT added."
fi
if [ "$first_match" = "done" ]; then
if [ "$only_once" = "true" ]; then
log "\t\tmain: Deactivating entry in rules-file as requested."
sed -i "${rule_line} s/^/#/g" ${rule_file}
fi
break
fi
else
log "\t\tmain: --- NOT adding timer because no more tuners available/to many parallel recordings for this time span. ---"
fi
else
log "\t\tmain: --- NOT adding timer because show was already recorded/planed before. ---"
fi
else
log "\t\tmain: --- NOT adding timer because of non-matching weekday. ---"
fi
else
log "\t\tmain: --- NOT adding timer because start_secs lies in the past. ---"
fi
done
displaylog "~T`scale2res 0150` $channelname ~T`scale2res 0300` $dowfilter ~T`scale2res 0425` $regex ~T`scale2res 0900` $shows_found Treffer"
}
fi
done # while loop
done # for loop
# After processing all rules from the rule-file, we go for the "sky-cinema" function, if activated.
parse_timespan # reset timespan
parse_flags # reset flags
parse_sky_cinema
if [ ! "$sky_cinema" = "" ]; then
log ""
log "\tmain: processing new movies at \"$sky_cinema\""
parse_timespan "$timespan"
parse_recdir
sky_moviedata=$(get_sky_moviedata)
echo "$sky_moviedata" | while read sky_date sky_movie; do
if sky_start_sec=$(date -d "$sky_date" +%s 2>/dev/null); then
now=$(date +%s)
if [ $sky_start_sec -lt $now ]; then
continue
fi
sky_start_sec=$((sky_start_sec \+ sky_start_sec_add))
sky_show_data=$(find_show_by_start_sec "$sky_cinema" "$sky_start_sec")
if [ ! "$sky_show_data" = "" ]; then
# split sky_show_data
begin_ifs_block
IFS='|'
set -- $sky_show_data
channelid=$1
start_sec=$2
stop_sec=$3
end_ifs_block
set -- $(get_corrected_start_stop_times $start_sec $stop_sec $event_type)
start_sec_corrected=$1
stop_sec_corrected=$2
dow=$(date -D %s -d ${start_sec} +%a)
parse_dowgroup
if [ $start_sec_corrected -gt $(date +%s) ]; then
if [ "$dow" = "$dowfilter" -o "$dowfilter" = "*" -o "$dowgroup" = "true" ]; then
add_timer "$sky_cinema" $channelid $start_sec_corrected $stop_sec_corrected $event_type $recdir
displaylog "~T`scale2res 0150` $sky_cinema ~T`scale2res 0300` $(date -D %s -d ${start_sec} '+%d.%m.%Y %H:%M') ~T`scale2res 0425` $sky_movie"
else
log "\t\tmain: NOT adding timer because of non-matching weekday."
fi
fi
fi
fi
done
fi
# Display some results to the screen, if the option -m is given
if [ $opt_menu ]; then
msgbox title="$NAME" size=20 refresh=0 cyclic=0 timeout=15 popup=$displaylogfile >/dev/null
fi
if [ $DEBUG_DISPLAYLOG == 1 ]; then
cat $displaylogfile
fi
log "$ME V$VERSION now performing cleanup .."
# Perform some cleanup before exiting
# maybe bundle the calls to all cleanup actions in one function ?
clean_historic_timer_file
clean_history_file
cleanup
# Perform post-actions, if activated.
log "Executing configured post-actions ... "
if [ -n "$POST_ACTION" ]; then
log "\tExecuting '$POST_ACTION'"
eval $POST_ACTION
log "\tDone."
else
log "\tNo post-action configured."
fi
# Perform final operation, if activated.
log "Performing final operation ... (END_SHUT_DOWN=$END_SHUT_DOWN)"
if [ $END_SHUT_DOWN -gt 0 ]; then
# wait while recording
while [ $DEBUG_DRY_RUN -eq 0 ] && [ $(wget -q -O - $webserver_url/control/setmode?status | dos2unix) = "on" ]
do
log "\tRecording is active, waiting with final operation for 900 seconds."
sleep 900
done
Stunde=$(date +%H)
if [ x"$(pidof EPGscan.sh)" = "x" ]; then
if [ $SHUT_DOWN_ALSO_DAY -eq 1 ] || [ $Stunde -lt 6 ]; then
case $END_SHUT_DOWN in
1)
if [ $DEBUG_DRY_RUN -eq 0 ]; then
log "\tSetting the box to standby in 2 seconds."
(sleep 2; wget -qO "/dev/null" "$webserver_url/control/standby?on") &
else
log "\tDEBUG DRY RUN: Would now set the box to standby in 2 seconds (using control/standby?on). Skipping."
fi
;;
2)
if [ $DEBUG_DRY_RUN -eq 0 ]; then
log "\tInitiating shutdown of the box in 2 seconds."
(sleep 2; wget -qO "/dev/null" "$webserver_url/control/shutdown") &
else
log "\tDEBUG DRY RUN: Would now shutdown the box in 2 seconds (using /control/shutdown). Skipping."
fi
;;
3)
if [ $DEBUG_DRY_RUN -eq 0 ]; then
log "\tHalting the system in 2 seconds."
(sleep 2; halt) &
else
log "\tDEBUG DRY RUN: Would now halt the box in 2 seconds (using command 'halt'). Skipping."
fi
;;
4)
if [ $DEBUG_DRY_RUN -eq 0 ]; then
log "\tRebooting the system in 2 seconds."
(sleep 2; reboot) &
else
log "\tDEBUG DRY RUN: Would now reboot the box in 2 seconds (using command 'reboot'). Skipping."
fi
;;
esac
else
log "\tSkipping final operation during day (SHUT_DOWN_ALSO_DAY=$SHUT_DOWN_ALSO_DAY)."
fi
else
log "\tIgnoring final operation since a running EPGscan was found!"
fi
else
log "\tFinal operation is deactivated!"
fi
log "$ME V$VERSION exiting."
#END SECTION "Main"
#######################################################################################