1 |
apollock |
8 |
#!/usr/bin/python |
2 |
|
|
|
3 |
|
|
import socket |
4 |
|
|
import datetime |
5 |
|
|
|
6 |
apollock |
10 |
class FrontendNotReady(Exception): |
7 |
|
|
pass |
8 |
|
|
|
9 |
|
|
|
10 |
|
|
class FrontendCmdFailure(Exception): |
11 |
|
|
pass |
12 |
|
|
|
13 |
|
|
|
14 |
apollock |
11 |
class FrontendInvalidInput(Exception): |
15 |
|
|
pass |
16 |
|
|
|
17 |
|
|
|
18 |
apollock |
8 |
class Frontend: |
19 |
|
|
"""Class for operating on a MythTV frontend""" |
20 |
|
|
|
21 |
|
|
def __init__(self, host, port=6546): |
22 |
|
|
self.host = host |
23 |
|
|
self.port = port |
24 |
|
|
self.ready = False |
25 |
|
|
self.socket = None |
26 |
apollock |
10 |
# We might as well raise an exception at instantiation if |
27 |
|
|
# the frontend is unavailable |
28 |
|
|
self._connect() |
29 |
apollock |
8 |
|
30 |
|
|
|
31 |
|
|
def _connect(self): |
32 |
|
|
"""Create a connection to the frontend""" |
33 |
|
|
|
34 |
|
|
self.socket = socket.socket() |
35 |
apollock |
10 |
try: |
36 |
|
|
self.socket.connect((self.host, self.port)) |
37 |
|
|
except socket.error: |
38 |
|
|
raise FrontendNotReady("Unable to connect to frontend") |
39 |
apollock |
8 |
if self.socket.recv(4096).splitlines()[-1] == '# ': |
40 |
|
|
self.ready = True |
41 |
|
|
|
42 |
|
|
|
43 |
|
|
def _sendcmd(self, cmd): |
44 |
|
|
"""Send a command to the frontend""" |
45 |
|
|
|
46 |
apollock |
10 |
self._connect() |
47 |
apollock |
8 |
if self.ready: |
48 |
|
|
self.socket.send("%s\r\n" % cmd) |
49 |
|
|
return self.socket.recv(4096).splitlines() |
50 |
|
|
|
51 |
|
|
|
52 |
|
|
def location(self): |
53 |
|
|
"""Return what location the front end is currently in""" |
54 |
|
|
|
55 |
|
|
result = self._sendcmd("query location")[0].split(" ") |
56 |
apollock |
10 |
location = Location() |
57 |
|
|
location.where = result[0] |
58 |
|
|
if location.where == 'Playback': |
59 |
|
|
location.program = ProgramInfo() |
60 |
|
|
location.program.what = result[1] |
61 |
|
|
location.program.position = result[2] |
62 |
|
|
location.program.length = result[4] |
63 |
|
|
location.program.speed = result[5] |
64 |
|
|
location.program.chanid = result[6] |
65 |
apollock |
8 |
recdate = result[7].split("T")[0].split("-") |
66 |
apollock |
10 |
location.program.recdate = datetime.date(int(recdate[0]), int(recdate[1]), int(recdate[2])) |
67 |
apollock |
8 |
rectime = result[7].split("T")[1].split(":") |
68 |
apollock |
10 |
location.program.rectime = datetime.time(int(rectime[0]), int(rectime[1]), int(rectime[2])) |
69 |
|
|
if location.program.what == 'Recorded': |
70 |
|
|
try: |
71 |
|
|
location.program.title = self.recording(location.program.chanid, location.program.recdate.isoformat(), location.program.rectime.isoformat()) |
72 |
|
|
except FrontendNotReady: |
73 |
|
|
location.program.title = None |
74 |
|
|
elif location.where == 'PlaybackBox': |
75 |
apollock |
8 |
pass |
76 |
apollock |
10 |
elif location.where == 'MainMenu': |
77 |
apollock |
8 |
pass |
78 |
|
|
|
79 |
|
|
return location |
80 |
|
|
|
81 |
|
|
|
82 |
|
|
def recording(self, chanid, recdate, rectime): |
83 |
|
|
"""Returns recording information for a specified recording""" |
84 |
|
|
|
85 |
|
|
result = self._sendcmd("query recording %s %sT%s" % |
86 |
|
|
(chanid, recdate, rectime))[0].split(" ") |
87 |
|
|
|
88 |
|
|
return " ".join(result[2:]) |
89 |
|
|
|
90 |
|
|
|
91 |
|
|
def pause(self): |
92 |
|
|
"""Pauses the playback""" |
93 |
|
|
|
94 |
|
|
result = self._sendcmd("play speed pause")[0] |
95 |
|
|
if result != "OK": |
96 |
|
|
#TODO(apollock): raise some sort of exception |
97 |
apollock |
10 |
raise FrontendCmdFailure("Frontend said '%s' in response to 'play speed pause' command" % result) |
98 |
apollock |
8 |
|
99 |
|
|
|
100 |
|
|
def play(self): |
101 |
|
|
"""Resumes playback""" |
102 |
|
|
result = self._sendcmd("play speed normal")[0] |
103 |
|
|
if result != "OK": |
104 |
apollock |
10 |
raise FrontendCmdFailure("Frontend said '%s' in response to 'play speed normal' command" % result) |
105 |
|
|
|
106 |
|
|
|
107 |
|
|
def isPaused(self): |
108 |
|
|
"""Returns if the the frontend is paused or not""" |
109 |
|
|
loc1 = self.location() |
110 |
|
|
if loc1.where != 'Playback': |
111 |
|
|
return False |
112 |
|
|
else: |
113 |
|
|
loc2 = self.location() |
114 |
|
|
if loc2.where != 'Playback': |
115 |
|
|
return False |
116 |
|
|
else: |
117 |
|
|
return loc1.program.position == loc2.program.position |
118 |
|
|
|
119 |
apollock |
11 |
def targets(self): |
120 |
|
|
"""Returns a list of targets that jump() accepts""" |
121 |
|
|
result = self._sendcmd("help jump") |
122 |
|
|
return [x.split(" ")[0] for x in result[3:]] |
123 |
apollock |
10 |
|
124 |
apollock |
11 |
|
125 |
|
|
def jump(self, target): |
126 |
|
|
"""Jumps to the specified location""" |
127 |
|
|
if target not in self.targets(): |
128 |
|
|
raise FrontendInvalidInput("Invalid jump target '%s'" % target) |
129 |
|
|
else: |
130 |
|
|
result = self._sendcmd("jump %s" % target)[0] |
131 |
|
|
if result != 'OK': |
132 |
|
|
raise FrontendCmdFailure("Frontend said '%s' in response to 'jump %s' command" % (result, target)) |
133 |
|
|
|
134 |
|
|
|
135 |
apollock |
10 |
class Location: |
136 |
|
|
"""Container for where the frontend currently is""" |
137 |
|
|
|
138 |
|
|
def __init__(self): |
139 |
|
|
self.program = None |
140 |
|
|
self.where = None |
141 |
|
|
|
142 |
|
|
|
143 |
|
|
class ProgramInfo: |
144 |
|
|
"""Container for program information""" |
145 |
|
|
|
146 |
|
|
def __init__(self): |
147 |
|
|
pass |