From e09439b06ef0ad8e3784b1a698596a561838a75c Mon Sep 17 00:00:00 2001 From: Britney Fransen Date: Sat, 11 Feb 2023 23:53:27 -0500 Subject: linhes-system: add lh_myth_status.py myth_mtc.py: fix some paths myth2videos: initial inclusion myth2mkv & myth2mp3: change /myth paths --- linhes/linhes-system/PKGBUILD | 16 +- linhes/linhes-system/lh_myth_status.py | 271 +++++++++++++++++++++++++++++++++ linhes/linhes-system/myth2mkv | 4 +- linhes/linhes-system/myth2mp3 | 6 +- linhes/linhes-system/myth2videos | 148 ++++++++++++++++++ linhes/linhes-system/myth_mtc.py | 4 +- 6 files changed, 435 insertions(+), 14 deletions(-) create mode 100755 linhes/linhes-system/lh_myth_status.py create mode 100755 linhes/linhes-system/myth2videos diff --git a/linhes/linhes-system/PKGBUILD b/linhes/linhes-system/PKGBUILD index 88194ce..966184b 100755 --- a/linhes/linhes-system/PKGBUILD +++ b/linhes/linhes-system/PKGBUILD @@ -1,21 +1,21 @@ pkgname=linhes-system pkgver=9.0.0 -pkgrel=42 +pkgrel=43 arch=('x86_64') #install=$pkgname.install pkgdesc="Everything that makes LinHES a system" license=('GPL2') -depends=('cronie' 'dbus-python' 'dvb-firmware' 'flatpak' 'firefox' 'glances' 'kdialog' 'libnotify' +depends=('cronie' 'dbus-python' 'dvb-firmware' 'expect' 'flatpak' 'firefox' 'glances' 'kdialog' 'libnotify' 'logrotate' 'linhes-templates' 'linhes-theme' 'mlocate' 'ncdu' 'openssh' 'pacman-contrib' 'rsyslog' 'ttf-overlock' 'wget' 'x11vnc') binfiles="add_storage.py balance_storage_groups.py empty_storage_groups.py remove_storage.py checkXFSfrag.sh enableIRWake.sh idle.py lh_system_start.sh lh_notify-send - lh_home_check.sh jobqueue_helper.py gen_lib_xml.py + lh_home_check.sh lh_myth_status.py jobqueue_helper.py gen_lib_xml.py diskspace.sh find_orphans.py optimize_mythdb.py myth_mtc.py misc_recent_recordings.pl misc_status_config.py misc_status_info.sh misc_upcoming_recordings.pl misc_which_recorder.pl create_media_dirs.sh be_check.py - myth2mkv myth2mp3 udev_link.sh" + myth2mkv myth2mp3 myth2videos udev_link.sh" source=($binfiles 'myth_mtc.cron' 'paccache.cron' 'flatpak_update.cron' 'xfs_defrag.cron' 'readme_is_xml' 'add_storage.readme' 'LinHES-release' 'lh_log_care.cron' @@ -33,12 +33,13 @@ sha256sums=('525bfe29b63d3ec5a17a32fa29745e24070020490c3f5b6dd6b03250348fb324' '644414148e514e4a56d68959cb427ddde4129a961cbf09cd1a0a39129d58a0b1' '6d4fb0ed1a5ed961b3a3884dce093118e50c2981a9cd5837d20abc5a6d4fd8aa' '87875d9e5f5ce18208f419698ce69b6bcbcd08955a57a4a13940e715af58b787' + '3b3d774701ee52054f6592258b98b9f18c3f7f9a93982ef07c7a3c7cf9445b82' '91bdec992bb2c933e15625c181f2195c402060b879168ebf35944cb064c904b9' '5cacfdd02833e5a3130d765573e772e6bd5030336ba86239c5e4db5ffa36fc69' 'ebdb3ee0212e0cc72526bb5e50a032573e1894acb7bf75617243b0b49aa1f8f2' '4d006f0fe3b13e67de1b961d611e81911905a30d140849dfdb8e5c0dc4da2f7c' 'e371c6a289c68fe200d7da856c20a8c579efa23178f4d62235f7359d7f6e49a1' - 'd63ce11809600e77ff187eee3751a8635045ef14c6333f1584f5e35f15a679a1' + '29bf341afcbd54ce9a040596fa6a86622bbfe08addad0c673f68661fb93642b4' 'd2d69b2bf6315bd37ff5f5b2f0cde8ab2fb89bae18f8796dc5208ffc1a9d743e' 'a3f8ba840853e4a189dd52520a6958f4030e1cc3391200a6aeef055fb469f0b4' '1819085bd2c9106482c5f243b95fddf3dae69212330ab76cb493add5c26a45a4' @@ -46,8 +47,9 @@ sha256sums=('525bfe29b63d3ec5a17a32fa29745e24070020490c3f5b6dd6b03250348fb324' '1d12a128a01dbf6327a80daab9edfdc57d393d02074d19c6a5bd54560cc6b0c0' 'bffcc13e4b480f720feb2b3c781bc4247c63303250c3d885022c699573d45a33' '51093acab5e5a4de51a55f4bdf7b935f4f69edf3d84f1c37db710853ec95eca8' - '9ea1b5583cd38f53bb79d9e4ccae91a028db0b6850162d7991b19122c564b9c9' - 'a81fdb81d8890e73b7891756623d536a133410ea43205b7152a295ad9ab8f3c9' + 'ad4ddabbc34d5e1b308ece33cabf91d750f44894c52a18325762dea026152973' + 'a961cfdc6f02b12fb445777dd2c144fed96306ca2f430cc8853ae307c759c1ad' + 'd8574104b75c6d41284488612ec5583c50a8dab438492fa42c47231add4cfc54' '6bdbf593d3e1348d1a8f7c4c17cb2e893f7e18ae355daf978173e669cfe3be80' '9e97b4d68c9e8988daacadd40f1de9f0b5945d870eba596a2ceb5e0c023fa9c0' '186203d3c0520bb3d611da99d33a7713e9c1563814285f1f101097234f214b2f' diff --git a/linhes/linhes-system/lh_myth_status.py b/linhes/linhes-system/lh_myth_status.py new file mode 100755 index 0000000..ea8c6f2 --- /dev/null +++ b/linhes/linhes-system/lh_myth_status.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +#This program is called on login to display the status of mythtv tuners & recording status +#Also will display alerts generated by xymon. If the location of xymon changes, this script needs to be updated. + +from MythTV import MythBE,MythDB,MythLog +import datetime,pytz,re,socket,subprocess,sys,time +from dateutil.parser import parse +from tzlocal import get_localzone + +import os,glob +from socket import gethostname; + +def formatTD(td): + days = td.days + hours = td.seconds // 3600 + minutes = (td.seconds % 3600) // 60 + seconds = td.seconds % 60 + + if days == 0: + day_string = "" + elif days > 1: + day_string = "%s days, " %days + else: + day_string = "%s day, " %days + + if hours > 1: + hour_string = "%s hours, " %hours + else: + hour_string = "%s hour, " %hours + + if minutes > 1: + minute_string = "%s minutes, " %minutes + else: + minute_string = "%s minute, " %minutes + + if seconds > 1: + second_string = "%s seconds" %seconds + else: + second_string = "%s second" %seconds + + return_string = '%s%s%s%s' % (day_string, hour_string, minute_string, second_string) + return return_string + +def print_alerts(): + dir_name = "/home/xymon/var/login_alerts" + out_alert="" + try: + os.chdir(dir_name) + except: + pass + #print " myth_status: Couldn't change dir to %s" %dir_name + + file_list=glob.glob("*") + + if len(file_list) == 0: + #print " myth_status: no alert files found" + pass + else: + for alert_file in file_list: + out_line='' + datahost = '' + dataservice = '' + datacolor = '' + datadown = '' + try: + #print " myth_staus: reading in %s" %alert_file + f=open(alert_file,'r') + lines=f.readlines() + f.close() + except: + #print " myth_status: Couldn't open %s for reading" %alert_file + continue + + for line in lines: + try: + data,value=line.split(":") + except: + continue #exception occured try the next line + + if data == 'HOST': + datahost = value.strip() + elif data == 'SERVICE': + dataservice = value.strip() + elif data == 'COLOR': + datacolor = value.strip() + elif data == 'DOWN': + datadown = value.strip() + sec=int(datadown) + td_sec = datetime.timedelta(seconds=sec) + td_sec_formated = formatTD(td_sec) + + out_line =" %s on %s %s for %s \n" %(dataservice, + datahost,datacolor.upper(), + td_sec_formated) + out_alert += out_line + + print("System Alerts:") + print("--------------") + if len(out_alert) > 0: + print(out_alert) + print(" Go to http://%s and click Health & Maintenance for more information." %gethostname()) + else: + print(" All systems OK") + + return + + +#------------------------------------------- + + +class tuner_recording_status: + def __init__ (self,num_upcoming): + + self.now = datetime.datetime.now(pytz.utc) + self.currTZ = get_localzone() + self.farout=99999999 + self.next_start_diff=datetime.timedelta(self.farout) + self.num_upcoming=num_upcoming + self.tuner_status_list=[] + self.conflict_list=[] + self.upcoming_list=[] + self.ur=0 + self.db_connection_status = self.check_database_connection() + if self.db_connection_status == 0: + self.tuner_status() + self.conflicts() + self.upcoming_recordings() + + def get_db_check_status(self): + return self.db_connection_status + + def check_database_connection(self): + rc=0 + try: + self.be = MythBE() + self.db = MythDB() + self.cursor = self.db.cursor() + except: + print("\nCouldn't connect to MythTV service for status") + rc=1 + return rc +#----- + def tuner_status(self): + a=self.be.getRecorderList() + for i in a: + outline='' + cmd="select cardtype,hostname,displayname from capturecard where cardid=%s;" %i + self.cursor.execute(cmd) + results=self.cursor.fetchall() + type = results[0][0] + hostname = results[0][1] + displayname = results[0][2] + id = i + try: + c=self.be.getCurrentRecording(i) + if c.title == None: + current_recording = "Idle" + else: + current_recording = "Recording %s" %c.title + outline = " Tuner %s - %s (%s) on %s : %s " %(id, displayname, type, hostname, current_recording) + self.tuner_status_list.append(outline) + except: + outline = " Tuner %s - %s (%s) on %s : %s " %(id, displayname, type, hostname, "Tuner Error") + self.tuner_status_list.append(outline) + + def get_tuner_status(self): + return self.tuner_status + + def print_tuner_status(self): + print("Tuner Status:") + print("-------------") + if len(self.tuner_status_list) > 0 : + for line in self.tuner_status_list: + print(line) + else: + print(" No tuners found") +#-------- + def upcoming_recordings(self): + + a=self.be.getUpcomingRecordings() + r=0 + for i in a: + r += 1 + if r > self.num_upcoming: + break + title_chan="%s (%s)" %(i.title, i.channame) + # convert timezone to local timezone + start_time=parse(str(i.starttime)) + start_time=start_time.astimezone(self.currTZ) + start_time_out=start_time.strftime("%a %b %d %I:%M%p") + self.upcoming_list.append([start_time_out,i.hostname, title_chan]) + + diff = start_time - self.now + if diff < self.next_start_diff : + self.next_start_diff = diff + + if self.next_start_diff == datetime.timedelta(self.farout): + self.ur="No recordings are scheduled" + else: + self.ur=formatTD(self.next_start_diff) + + def get_upcoming_recordings(self): + return self.upcoming_list + + def print_upcoming_recordings(self): + #print self.get_upcoming_recordings() + print("") + print("Upcoming Recordings (Next %s Scheduled):" %(self.num_upcoming)) + print("----------------------------------------") + if len(self.get_upcoming_recordings()) > 0: + for i in self.get_upcoming_recordings(): + #print " %s - %s - %s" %(start_time_out,i.hostname, title_chan) + print(" %s - %s - %s" %(i[0],i[1],i[2])) + else: + print(" No upcoming recordings") + pass + + def get_next_start_time(self): + return self.ur + + def print_next_start_time(self): + print("") + print("The next recording starts in:") + print("-----------------------------") + print(" %s" %(self.get_next_start_time())) + print("") + +#----- + + def conflicts(self): + a=self.be.getConflictedRecordings() + for i in a: + out_line='' + title_chan="%s (%s)" %(i.title, i.channame) + # convert timezone to local timezone + start_time=parse(str(i.starttime)) + start_time=start_time.astimezone(self.currTZ) + start_time_out=start_time.strftime("%a %b %d %I:%M%p") + out_line=(start_time_out,i.hostname,title_chan) + self.conflict_list.append(out_line) + + def get_conflict_list(self): + return self.conflict_list + + def print_conflict_list(self): + print("") + print("Recording Conflicts:") + print("--------------------") + if len(self.get_conflict_list()) > 0: + for i in self.get_conflict_list(): + print(" %s - %s - %s" %(i[0],i[1],i[2])) + else: + print(" No conflicts") + +#header="#"*60 + + +def go(): + welcomeFile=open("/etc/LinHES-release", "r") + print("Welcome to %s on %s\n" %(welcomeFile.readline().rstrip(), socket.gethostname())) + tuner = tuner_recording_status(12) + if tuner.get_db_check_status() == 0: + tuner.print_tuner_status() + tuner.print_upcoming_recordings() + tuner.print_conflict_list() + tuner.print_next_start_time() + print_alerts() + + +if __name__ == "__main__": + go() diff --git a/linhes/linhes-system/myth2mkv b/linhes/linhes-system/myth2mkv index 51a0c9b..6edb804 100755 --- a/linhes/linhes-system/myth2mkv +++ b/linhes/linhes-system/myth2mkv @@ -31,12 +31,12 @@ # # ######################## -OUTDIR=/myth/video +OUTDIR=/data/storage/disk0/media/video LOGPATH=/var/log/mythtv LOGFILE=${LOGPATH}/myth2mkv-$$.log # TMPDIR is for large transient files -TMPDIR=/myth/tmp +TMPDIR=/data/storage/disk0/media/tmp # x264 tuning: # Tune x264 based on content. Valid options for TUNING are: diff --git a/linhes/linhes-system/myth2mp3 b/linhes/linhes-system/myth2mp3 index ebc588b..e5960be 100755 --- a/linhes/linhes-system/myth2mp3 +++ b/linhes/linhes-system/myth2mp3 @@ -16,12 +16,12 @@ BITRATE=256k #ie. 128k, 160k, 192k, 224k, 256k USECUTLIST=Y #Y or N # where the converted audio is stored -OUT_DIR=/myth/music +OUT_DIR=/data/storage/disk0/media/music # create temp filename so multiple instances won't conflict TMPNAME=toMP3-$$ -TMPFILE=/myth/tmp/$TMPNAME -TMPCUTFILE=/myth/tmp/$TMPNAME.mpg +TMPFILE=/data/storage/disk0/media/tmp/$TMPNAME +TMPCUTFILE=/data/storage/disk0/media/tmp/$TMPNAME.mpg FFINPUTFILE=$1 TITLE=`echo $2 | sed 's/\//_/g'` diff --git a/linhes/linhes-system/myth2videos b/linhes/linhes-system/myth2videos new file mode 100755 index 0000000..c4d3246 --- /dev/null +++ b/linhes/linhes-system/myth2videos @@ -0,0 +1,148 @@ +#!/bin/sh +# copy recording to videos +# version 0.3 + +# usage: +# first parameter must be %DIR%/%FILE% of the recording +# second parameter must be the desired base name of the output +# third parameter must be %CHANID% if you set USECUTLIST=Y +# fourth parameter must be %STARTTIME% if you set USECUTLIST=Y +# fifth parameter must be %JOBID% for the User Job status to be updated in MythTV +# in the mythtv setup screen invoke this script like this: +# MYTHTV User Job Command: +# /usr/bin/myth2videos "%DIR%/%FILE%" "%TITLE% - %SUBTITLE%" "%CHANID%" "%STARTTIME%" "%JOBID%" + +# options: +USECUTLIST=Y # Y or N + +# where the video is stored +OUT_DIR=/data/storage/disk0/media/video + + +#------FUNCTIONS--------------- +update_comment() +# Arg_1 = COMMENT +{ +if [ $NO_JOBID -eq 0 ]; then + `jobqueue_helper.py -j ${JOBID} -cs "${1}"` +fi +} + +update_status() +# Arg_1 = status code +{ +if [ $NO_JOBID -eq 0 ]; then + `jobqueue_helper.py -j ${JOBID} -cs "${1}"` +fi +} + +check_myth_jobcmds() +# check the myth database for stop pause or resume commands +{ +if [ $NO_JOBID -eq 0 ]; then + CURRENT_CMD=`jobqueue_helper.py -m "select cmds from jobqueue where id = ${JOBID}"` + case "$CURRENT_CMD" in + # JOB_RUN + 0) ;; + # JOB_PAUSE + 1) `jobqueue_helper.py -j ${JOBID} -ss 6`;; + # JOB_RESUME + 2) `jobqueue_helper.py -j ${JOBID} -ss 4` + `jobqueue_helper.py -j ${JOBID} -cmds 0`;; + # JOB_STOP + 4) `jobqueue_helper.py -j ${JOBID} -ss 5` + `jobqueue_helper.py -j ${JOBID} -cmds 0` + clean_up_files + echo "Copy Cancelled" >> $LOGFILE + `jobqueue_helper.py -j ${JOBID} -ss 320` + exit ;; + esac +fi +} + +clean_up_files() +# clean up left over files +{ +unlink $TMPFILE 2> /dev/null +unlink $TMPFILE.map 2> /dev/null +} + +#-------MAIN SCRIPT------------ + +# check if %JOBID% is passed from command line +JOBID=${5} +if [ -z "$JOBID" ]; then + NO_JOBID=1 +else + NO_JOBID=0 +fi + +# create temp filename so multiple instances won't conflict +TMPNAME=toVIDEOS-$$ +TMPFILE=/data/storage/disk0/media/tmp/$TMPNAME.mpg +MENINPUTFILE=$1 +TITLE=`echo $2 | sed 's/\//_/g'` + +# log file location +LOGFILE=/var/log/mythtv/myth2videos.log +CDate="`date`" +echo "" >> $LOGFILE +echo $CDate >> $LOGFILE +echo "File to copy: $MENINPUTFILE Name: $TITLE" >> $LOGFILE +echo "$2 $3 $4 $5" >> $LOGFILE + +# start timer +beforetime="$(date +%s)" + +check_myth_jobcmds + +# check if using cutlist +if [ $USECUTLIST = Y ]; then + MYTHCOMMFRAMES=`mythutil --getcutlist --chanid "$3" --starttime "$4" | grep 'Cutlist:' | cut -d \ -f 2` + echo $MYTHCOMMFRAMES + if [ -n "$MYTHCOMMFRAMES" ]; then + echo "Extracting Cutlist..." >> $LOGFILE + update_comment "Extracting Cutlist..." + /usr/bin/nice -n19 /usr/bin/mythtranscode --chanid "$3" --starttime "$4" --outfile "$TMPFILE" --mpeg2 --honorcutlist + else + update_comment "Copying Recording..." + cp "$MENINPUTFILE" "$TMPFILE" + fi +elif [ $USECUTLIST = N ]; then + update_comment "Copying Recording..." + cp "$MENINPUTFILE" "$TMPFILE" +fi + +# make output filename unique +OUTPUTFILE=$OUT_DIR/$TITLE.mpg +i=1 +while [ -e "$OUTPUTFILE" ] +do + OUTPUTFILE=$OUT_DIR/$TITLE-$i.mpg + i=`expr $i + 1` +done + +# move temp file to output location +chown mythtv "$TMPFILE" && mv "$TMPFILE" "$OUTPUTFILE" + +# stop timer +aftertime="$(date +%s)" +seconds="$(expr $aftertime - $beforetime)" + +ERROR=$? +if [ $ERROR -eq 0 ]; then + echo "File Encoded Successfully: $OUTPUTFILE" >> $LOGFILE + hours=$((seconds / 3600)) + seconds=$((seconds % 3600)) + minutes=$((seconds / 60)) + seconds=$((seconds % 60)) + echo "Encoding took $hours hour(s) $minutes minute(s) $seconds second(s) @ $current_FPS fps." >> $LOGFILE + update_status 272 + update_comment "Encode Successful. Encoding Time: $hours hour(s) $minutes minute(s) $seconds second(s)" +else + update_status 304 + update_comment "Encode Failed. Exit status: $ERROR" + echo "ERROR: $ERROR" >> $LOGFILE +fi + +clean_up_files diff --git a/linhes/linhes-system/myth_mtc.py b/linhes/linhes-system/myth_mtc.py index 953fb96..3cfbfb1 100755 --- a/linhes/linhes-system/myth_mtc.py +++ b/linhes/linhes-system/myth_mtc.py @@ -81,7 +81,7 @@ def cleanup_inuseprograms(): def bail_if_another_is_running(): cmd = shlex.split("pgrep -u {} -f {}".format(os.getuid(), __file__)) - pids = subprocess.check_output(cmd).strip().split('\n') + pids = subprocess.check_output(cmd).decode('utf-8').strip().split('\n') if len(pids) > 1: pids.remove("{}".format(os.getpid())) print("Exiting! Found {} is already running (pids): {}".format( @@ -100,7 +100,7 @@ def run_stuff(): print("No system idle check will be done.") idle = 0 else: - idle = subprocess.call(["/usr/bin/python2", "/usr/LH/bin/idle.py"]) + idle = subprocess.call(["/usr/bin/python", "/usr/bin/idle.py"]) if not idle: if ("--check_home" in sys.argv) or runall: -- cgit v0.12