#!/bin/sh # pr-auto-timer - automatic TV timer creation for neutrino # Copyright (C) 2012-2013 Patrick Reinhardt, pr-cs 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 (" & and '). # 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//"/\"} res=${res//'/\'} res=${res//&/\&} #res=${res//>/\>} #res=${res//</\<} # ...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); } // { channel_id = ""; start_sec = ""; stop_sec = ""; description = ""; info1 = ""; info2_1 = ""; info2_2 = ""; info2_open = 0; } /[0-9a-z]+<\/channel_id>/ { channel_id = gensub(/<\/?[^>]+>/, "", "g"); } /[0-9]+<\/start_sec>/ { start_sec = gensub(/<\/?[^>]+>/, "", "g"); } /[0-9]+<\/stop_sec>/ { stop_sec = gensub(/<\/?[^>]+>/, "", "g"); } /.*<\/info1>/ { info1 = gensub(/(<\/info1>)/, "", "g"); } /.*/ { info2_1 = gensub(/(<\/info2>)/, "", "g"); info2_open = 1; } /.*<\/info2>/ { info2_2 = gensub(/(<\/info2>)/, "", "g"); info2_open = 0; } # This 'captures' also lines inbetween newlines for info2 !/.*info2.*/ { if (info2_open) { info2_1=info2_1 " " $0 } } /<\/description>/ { description = gensub(/(<\/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); } // { channel_id = ""; start_sec = ""; stop_sec = ""; description = ""; info1 = ""; info2_1 = ""; info2_2 = ""; info2_open = 0; } /[0-9a-z]+<\/channel_id>/ { channel_id = gensub(/<\/?[^>]+>/, "", "g"); } /[0-9]+<\/start_sec>/ { start_sec = gensub(/<\/?[^>]+>/, "", "g"); } /[0-9]+<\/stop_sec>/ { stop_sec = gensub(/<\/?[^>]+>/, "", "g"); } /.*<\/info1>/ { info1 = gensub(/(<\/info1>)/, "", "g"); } /.*/ { info2_1 = gensub(/(<\/info2>)/, "", "g"); info2_open = 1; } /.*<\/info2>/ { info2_2 = gensub(/(<\/info2>)/, "", "g"); info2_open = 0; } # This 'captures' also lines inbetween newlines for info2 !/.*info2.*/ { if (info2_open) { info2_1=info2_1 " " $0 } } /<\/description>/ { description = gensub(/(<\/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///g;s/<\/epgtitle>//g' | html2text) info1=$(grep info1 "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s///g;s/<\/info1>//g' | html2text) info2=$(grep "" "$show" | sed 's/^[[:space:]]*//g;s/ *$//g;s/ \{1,\}/ /g;s///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; } /

/ { found="movie"; movie=""; next; } /<\/h3>/ { found=false; next; } /

|\/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 (/'/, "'\''"); 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" #######################################################################################