Difference between revisions of "Mythremctl.py"

From MythTV Official Wiki
Jump to: navigation, search
m
(Update script to remove the 1 second delay when pressing the escape key.)
 
(2 intermediate revisions by one other user not shown)
Line 6: Line 6:
 
|category=Remote Control
 
|category=Remote Control
 
|file=mythremctl.py
 
|file=mythremctl.py
|S23=yes
+
|S24=yes
|S231=yes}}
+
|S241=yes
 +
|S25=yes}}
  
 
Python-based remote control using the [[Frontend control socket]] to send keys and commands to mythfrontend.
 
Python-based remote control using the [[Frontend control socket]] to send keys and commands to mythfrontend.
Line 14: Line 15:
 
<pre>
 
<pre>
 
#!/usr/bin/env python
 
#!/usr/bin/env python
from MythTV import MythDB, MythError, MythLog
+
from MythTV import MythDB, MythError, MythLog, Frontend
from curses import wrapper, ascii
+
 
from time   import sleep
+
from datetime import datetime, timedelta
import sys, socket, curses, re
+
from curses   import wrapper, ascii
 +
from time     import sleep
 +
import sys, socket, curses, re, os
  
 
#note for ticket, on-screen-keyboard remotely does not have focus
 
#note for ticket, on-screen-keyboard remotely does not have focus
  
 
MythLog._setlevel('none')
 
MythLog._setlevel('none')
myth = MythDB()
 
 
frontend = None
 
frontend = None
 
keys = {        9:'tab',            10:'enter',   
 
27:'escape',    32:'space',        35:'poundsign',
 
36:'dollar',    37:'percent',      38:'ampersand',
 
40:'parenleft', 41:'parenright',    42:'asterisk',
 
43:'plus',      44:'comma',        45:'minus',
 
46:'period',    47:'/',            58:'colon',
 
59:'semicolon', 60:'less',          61:'equal',
 
62:'greater',  63:'question',      91:'bracketleft',
 
92:'backslash', 93:'bracketright',  95:'underscore',
 
124:'pipe',    127:'backspace',    258:'down',
 
259:'up',      260:'left',        261:'right',
 
269:'f5',      270:'f6',          271:'f7',
 
272:'f8',      273:'f9',          274:'f10',
 
275:'f11',      276:'f12',          330:'delete',
 
331:'insert',  338:'pagedown',    339:'pageup'}
 
  
 
rplaytype = re.compile('Playback ([a-zA-Z]+)')
 
rplaytype = re.compile('Playback ([a-zA-Z]+)')
Line 60: Line 46:
 
     window.addstr(y,x,string)
 
     window.addstr(y,x,string)
  
def query_time(w):
+
def query_time(w, _dat=[0]):
     time = frontend.sendQuery('time').split('T')[1]
+
     if _dat[0] == 0:
 +
        ltime  = datetime.now()
 +
        fetime  = frontend.getTime()
 +
        _dat[0] = fetime - ltime
 +
    time = datetime.now()+_dat[0]
 
     w.erase()
 
     w.erase()
     w.border()
+
     w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
     align(1,w,1,time)
+
            curses.ACS_HLINE,    curses.ACS_HLINE,
 +
            curses.ACS_ULCORNER, curses.ACS_TTEE,
 +
            curses.ACS_LTEE,    curses.ACS_RTEE)
 +
     align(1,w,1,time.strftime('%H:%M:%S'))
 
     w.noutrefresh()
 
     w.noutrefresh()
  
def query_load(w):
+
def query_load(w, _dat=[0]):
     loads = frontend.sendQuery('load').split(' ')
+
     if _dat[0] == 0:
 +
        _dat[0] = datetime.now()
 +
    now = datetime.now()
 +
    if _dat[0] > now:
 +
        return
 +
    _dat[0] = now+timedelta(seconds=5)
 +
 
 +
    loads = frontend.getLoad()
 
     w.erase()
 
     w.erase()
     w.border()
+
     w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
 +
            curses.ACS_HLINE,    curses.ACS_HLINE,
 +
            curses.ACS_LTEE,    curses.ACS_RTEE,
 +
            curses.ACS_LTEE,    curses.ACS_RTEE)
 
     align(0,w,2,'loads')
 
     align(0,w,2,'loads')
     align(2,w,1,'1:     ')
+
     align(2,w,1,' 1: {0:0.2f}'.format(loads[0]))
     align(2,w,2,'5:     ')
+
     align(2,w,2,' 5: {0:0.2f}'.format(loads[1]))
     align(2,w,3,'15:     ')
+
     align(2,w,3,'15: {0:0.2f}'.format(loads[2]))
    align(2,w,1,loads[0],5)
 
    align(2,w,2,loads[1],5)
 
    align(2,w,3,loads[2],5)
 
 
     w.noutrefresh()
 
     w.noutrefresh()
  
def query_loc(w):
+
def query_loc(w, _dat=[0]):
 +
    if _dat[0] == 0:
 +
        _dat[0] = datetime.now()
 +
    now = datetime.now()
 +
    if _dat[0] > now:
 +
        return
 +
    _dat[0] = now+timedelta(seconds=5)
 +
 
 
     loc = frontend.sendQuery('location')
 
     loc = frontend.sendQuery('location')
 
     pb = rplaytype.match(loc)
 
     pb = rplaytype.match(loc)
 
     w.erase()
 
     w.erase()
     w.border()
+
     w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
 +
            curses.ACS_HLINE,    curses.ACS_HLINE,
 +
            curses.ACS_TTEE,    curses.ACS_URCORNER,
 +
            curses.ACS_BTEE,    curses.ACS_LRCORNER)
 
     if pb:
 
     if pb:
 
         if pb.group(1) == 'Video':
 
         if pb.group(1) == 'Video':
Line 92: Line 102:
 
         else:
 
         else:
 
             pb = rrecorded.match(loc)
 
             pb = rrecorded.match(loc)
 +
            if pb is None:
 +
                return
 
             if pb.group(1) == 'Recorded':
 
             if pb.group(1) == 'Recorded':
 
                 show = frontend.sendQuery('recording %s %s' \
 
                 show = frontend.sendQuery('recording %s %s' \
Line 108: Line 120:
 
     w.noutrefresh()
 
     w.noutrefresh()
  
def query_mem(w):
+
def query_mem(w, _dat=[0]):
     mem = frontend.sendQuery('memstats').split(' ')
+
     if _dat[0] == 0:
 +
        _dat[0] = datetime.now()
 +
    now = datetime.now()
 +
    if _dat[0] > now:
 +
        return
 +
    _dat[0] = now+timedelta(seconds=15)
 +
 
 +
    mem = frontend.getMemory()
 
     w.erase()
 
     w.erase()
     w.border()
+
     w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
 +
            curses.ACS_HLINE,    curses.ACS_HLINE,
 +
            curses.ACS_LTEE,    curses.ACS_RTEE,
 +
            curses.ACS_LLCORNER, curses.ACS_BTEE)
 
     align(0,w,1,'phy:')
 
     align(0,w,1,'phy:')
 
     align(0,w,2,'swp:')
 
     align(0,w,2,'swp:')
     align(2,w,1,"%sM/%sM" % (mem[1],mem[0]))
+
     align(2,w,1,"%sM/%sM" % (mem['freemem'],mem['totalmem']))
     align(2,w,2,"%sM/%sM" % (mem[3],mem[2]))
+
     align(2,w,2,"%sM/%sM" % (mem['freeswap'],mem['totalswap']))
 
     w.noutrefresh()
 
     w.noutrefresh()
  
Line 132: Line 154:
 
     align(2,conn,1,frontend.host)
 
     align(2,conn,1,frontend.host)
 
     align(2,conn,2,"%s:%d" % frontend.socket.getpeername())
 
     align(2,conn,2,"%s:%d" % frontend.socket.getpeername())
     conn.border()
+
     conn.border(curses.ACS_VLINE,    curses.ACS_VLINE,
 +
                curses.ACS_HLINE,    curses.ACS_HLINE,
 +
                curses.ACS_LTEE,    curses.ACS_RTEE,
 +
                curses.ACS_LTEE,    curses.ACS_RTEE)
  
 
     time = w.derwin(3,20,0,0)
 
     time = w.derwin(3,20,0,0)
Line 152: Line 177:
 
             a = w.getch()
 
             a = w.getch()
 
             curses.flushinp()
 
             curses.flushinp()
             if a == -1:
+
             frontend.key[a]
                continue
 
            if a in keys:
 
                s = keys[a]
 
            elif ascii.isalnum(a):
 
                s = chr(a)
 
            else:
 
                continue
 
            frontend.sendKey(s)
 
 
         except KeyboardInterrupt:
 
         except KeyboardInterrupt:
 
             break
 
             break
Line 168: Line 185:
 
             print "Remote side closed connection..."
 
             print "Remote side closed connection..."
 
             break
 
             break
 +
        except ValueError:
 +
            # assume an frontend stalled, opening a file
 +
            pass
 
         except:
 
         except:
 
             raise
 
             raise
 
 
 
 
 
 
  
 
if __name__ == '__main__':
 
if __name__ == '__main__':
     frontends = None
+
     if not os.environ.has_key('ESCDELAY'):
 +
        os.environ['ESCDELAY'] = '25'
 +
   
 
     if len(sys.argv) == 2:
 
     if len(sys.argv) == 2:
 
         try:
 
         try:
             frontend = myth.getFrontend(sys.argv[1])
+
            db = MythDB()
 +
             frontend = db.getFrontend(sys.argv[1])
 +
            wrapper(main)
 
         except socket.timeout:
 
         except socket.timeout:
 
             print "Could not connect to "+sys.argv[1]
 
             print "Could not connect to "+sys.argv[1]
Line 192: Line 210:
 
         except:
 
         except:
 
             raise
 
             raise
     if frontend is None:
+
     else:
 
         print "Please choose from the following available frontends:"
 
         print "Please choose from the following available frontends:"
    while frontend is None:
+
        frontends = None
        if frontends is None:
+
        while frontend is None:
            frontends = myth.getFrontends()
+
            if frontends is None:
            if len(frontends) == 0:
+
                frontends = list(Frontend.fromUPNP())
                print "No frontends detected"
+
                if len(frontends) == 0:
 +
                    print "No frontends detected"
 +
                    sys.exit()
 +
            for i,f in enumerate(frontends):
 +
                print "%d. %s" % (i+1, f)
 +
            try:
 +
                i = int(raw_input('> '))-1
 +
                frontend = frontends[i]
 +
                wrapper(main)
 +
            except KeyboardInterrupt:
 +
                sys.exit()
 +
            except EOFError:
 
                 sys.exit()
 
                 sys.exit()
        for i, x in enumerate(frontends):
+
            except IndexError:
            print "%d. %s" % (i+1, x)
+
                print "This input requires a value between 1 and %d" % len(frontends)
        try:
+
             except:
            i = int(raw_input('> '))-1
+
                print "This input will only accept a number. Use Ctrl-C to exit"
            if 0 <= i < len(frontends):
 
                frontend = frontends[i]
 
        except KeyboardInterrupt:
 
             sys.exit()
 
        except EOFError:
 
            sys.exit()
 
        except:
 
            print "This input will only accept a number. Use Crtl-C to exit"
 
    wrapper(main)
 
 
</pre>
 
</pre>
 
}}
 
}}

Latest revision as of 18:43, 5 September 2012

Important.png Note: The correct title of this article is mythremctl.py. It appears incorrectly here due to technical restrictions.


Author Raymond Wagner
Description A simple curses-based interface for manipulating mythfrontend using the control socket.
Supports Version24.png  Version241.png Version25.png  


Python-based remote control using the Frontend control socket to send keys and commands to mythfrontend.


PythonIcon.png mythremctl.py

#!/usr/bin/env python
from MythTV import MythDB, MythError, MythLog, Frontend

from datetime import datetime, timedelta
from curses   import wrapper, ascii
from time     import sleep
import sys, socket, curses, re, os

#note for ticket, on-screen-keyboard remotely does not have focus

MythLog._setlevel('none')
frontend = None

rplaytype = re.compile('Playback ([a-zA-Z]+)')
rrecorded = re.compile('Playback ([a-zA-Z]+) ([\d:]+) of ([\d:]+) ([-0-9\.]+x) (\d*) ([0-9-T:]+)')
rlivetv   = rrecorded
rvideo    = re.compile('Playback [a-zA-Z]+ ([\d:]+) ([-0-9\.]+x) .*/(.*) \d+ [\.\d]+')
rrname    = re.compile('\d+ [0-9-T:]+ (.*)')
rlname    = re.compile('\d+ [0-9-T: ]+ (.*)')

def align(side, window, y, string, flush=0):
    w = window.getmaxyx()[1]-1
    if len(string) > w:
        string = string[:w]
    if side == 0:
        x = 1
    elif side == 1:
        x = (w-len(string))/2+1
    elif side == 2:
        x = w-len(string)
    window.addstr(y,x,string)

def query_time(w, _dat=[0]):
    if _dat[0] == 0:
        ltime   = datetime.now()
        fetime  = frontend.getTime()
        _dat[0] = fetime - ltime
    time = datetime.now()+_dat[0]
    w.erase()
    w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
             curses.ACS_HLINE,    curses.ACS_HLINE,
             curses.ACS_ULCORNER, curses.ACS_TTEE,
             curses.ACS_LTEE,     curses.ACS_RTEE)
    align(1,w,1,time.strftime('%H:%M:%S'))
    w.noutrefresh()

def query_load(w, _dat=[0]):
    if _dat[0] == 0:
        _dat[0] = datetime.now()
    now = datetime.now()
    if _dat[0] > now:
        return
    _dat[0] = now+timedelta(seconds=5)

    loads = frontend.getLoad()
    w.erase()
    w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
             curses.ACS_HLINE,    curses.ACS_HLINE,
             curses.ACS_LTEE,     curses.ACS_RTEE,
             curses.ACS_LTEE,     curses.ACS_RTEE)
    align(0,w,2,'loads')
    align(2,w,1,' 1: {0:0.2f}'.format(loads[0]))
    align(2,w,2,' 5: {0:0.2f}'.format(loads[1]))
    align(2,w,3,'15: {0:0.2f}'.format(loads[2]))
    w.noutrefresh()

def query_loc(w, _dat=[0]):
    if _dat[0] == 0:
        _dat[0] = datetime.now()
    now = datetime.now()
    if _dat[0] > now:
        return
    _dat[0] = now+timedelta(seconds=5)

    loc = frontend.sendQuery('location')
    pb = rplaytype.match(loc)
    w.erase()
    w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
             curses.ACS_HLINE,    curses.ACS_HLINE,
             curses.ACS_TTEE,     curses.ACS_URCORNER,
             curses.ACS_BTEE,     curses.ACS_LRCORNER)
    if pb:
        if pb.group(1) == 'Video':
            pb = rvideo.match(loc)
            align(0,w,1,'  Playback: %s' % pb.group(3))
            align(0,w,2,'     %s @ %s' % (pb.group(1),pb.group(2)))
        else:
            pb = rrecorded.match(loc)
            if pb is None:
                return
            if pb.group(1) == 'Recorded':
                show = frontend.sendQuery('recording %s %s' \
                                            % (pb.group(5),pb.group(6)))
                name = rrname.match(show).group(1)
                align(0,w,1,'  Playback: %s' % name)
                
            elif pb.group(1) == 'LiveTV':
                show = frontend.sendQuery('liveTV %s' % pb.group(5))
                name = rlname.match(show).group(1)
                align(0,w,1,'  LiveTV: %s - %s' % (pb.group(5),name))
            align(0,w,2,'      %s of %s @ %s' \
                                % (pb.group(2),pb.group(3),pb.group(4)))
    else:
        align(0,w,1,'  '+loc)
    w.noutrefresh()

def query_mem(w, _dat=[0]):
    if _dat[0] == 0:
        _dat[0] = datetime.now()
    now = datetime.now()
    if _dat[0] > now:
        return
    _dat[0] = now+timedelta(seconds=15)

    mem = frontend.getMemory()
    w.erase()
    w.border(curses.ACS_VLINE,    curses.ACS_VLINE,
             curses.ACS_HLINE,    curses.ACS_HLINE,
             curses.ACS_LTEE,     curses.ACS_RTEE,
             curses.ACS_LLCORNER, curses.ACS_BTEE)
    align(0,w,1,'phy:')
    align(0,w,2,'swp:')
    align(2,w,1,"%sM/%sM" % (mem['freemem'],mem['totalmem']))
    align(2,w,2,"%sM/%sM" % (mem['freeswap'],mem['totalswap']))
    w.noutrefresh()

def main(w):
    curses.halfdelay(10)
    frontend.connect()
    y,x = w.getmaxyx()

    mem = w.derwin(4,20,9,0)
    query_mem(mem)

    load = w.derwin(5,20,5,0)
    query_load(load)

    conn = w.derwin(4,20,2,0)
    align(2,conn,1,frontend.host)
    align(2,conn,2,"%s:%d" % frontend.socket.getpeername())
    conn.border(curses.ACS_VLINE,    curses.ACS_VLINE,
                curses.ACS_HLINE,    curses.ACS_HLINE,
                curses.ACS_LTEE,     curses.ACS_RTEE,
                curses.ACS_LTEE,     curses.ACS_RTEE)

    time = w.derwin(3,20,0,0)
    query_time(time)

    loc = w.derwin(13,x-20,0,19)
    loc.timeout(1)
    while True:
        a = None
        s = None
        try:
            query_time(time)
            query_load(load)
            query_loc(loc)
            query_mem(mem)
            align(1,w,0,' MythFrontend Remote Socket Interface ')
            curses.doupdate()

            a = w.getch()
            curses.flushinp()
            frontend.key[a]
        except KeyboardInterrupt:
            break
        except EOFError:
            break
        except MythError:
            print "Remote side closed connection..."
            break
        except ValueError:
            # assume an frontend stalled, opening a file
            pass
        except:
            raise

if __name__ == '__main__':
    if not os.environ.has_key('ESCDELAY'):
        os.environ['ESCDELAY'] = '25'
    
    if len(sys.argv) == 2:
        try:
            db = MythDB()
            frontend = db.getFrontend(sys.argv[1])
            wrapper(main)
        except socket.timeout:
            print "Could not connect to "+sys.argv[1]
            pass
        except TypeError:
            print sys.argv[1]+" does not exist"
            pass
        except KeyboardInterrupt:
            sys.exit()
        except:
            raise
    else:
        print "Please choose from the following available frontends:"
        frontends = None
        while frontend is None:
            if frontends is None:
                frontends = list(Frontend.fromUPNP())
                if len(frontends) == 0:
                    print "No frontends detected"
                    sys.exit()
            for i,f in enumerate(frontends):
                print "%d. %s" % (i+1, f)
            try:
                i = int(raw_input('> '))-1
                frontend = frontends[i]
                wrapper(main)
            except KeyboardInterrupt:
                sys.exit()
            except EOFError:
                sys.exit()
            except IndexError:
                print "This input requires a value between 1 and %d" % len(frontends)
            except:
                print "This input will only accept a number. Use Ctrl-C to exit"