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