#!/usr/bin/python import socket, sys, string, select debugging = 1 # Utility functions def debug(str): if debugging: print str def debug_on(): global debugging debugging = 1 def debug_off(): global debugging debugging = 0 def receive_reply(sock): ret = "" count = int(sock.recv(8)) debug("REPLY LEN: %d" % count) while count > 0: ret = ret + sock.recv(65535) count = count - len(ret) debug("REPLY: '%s'" % ret) return ret def send_request(sock, req): msg = "%-8d%s" % (len(req), req) debug("MESSAGE: '%s'" % msg) sock.send(msg) reply = receive_reply(sock) return reply def send_message(sock, req): msg = "%-8d%s" % (len(req), req) debug("MESSAGE: '%s'" % msg) sock.send(msg) # Classes (i.e. structures) class connection: def __init__(self, host, port): self.host = host self.port = port self.control = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.control.connect((self.host, self.port)) reply = send_request(self.control, "MYTH_PROTO_VERSION 1"); debug("REPLY: '%s'" % reply) reply = send_request(self.control, "ANN Playback %s 0" % socket.gethostname()) debug("REPLY: '%s'" % reply) if reply != "OK": print "Cannot establish player" reply = send_request(self.control, "[]:[]QUERY_IS_ACTIVE_BACKEND[]:[]%s" % socket.gethostname()); debug("REPLY: '%s'" % reply) class programinfo: # Program information, describes a recording in detail. # Used for file transfers. def __init__(self, r): spl = r.split("[]:[]") self.title = spl[0] self.subtitle = spl[1] self.description = spl[2] self.category = spl[3] self.chanId = spl[4] self.chanstr = spl[5] self.chansign = spl[6] self.channame = spl[7] self.pathname = spl[8] self.Start = long(spl[9]) self.Length = long(spl[10]) self.start_ts= spl[11] self.end_ts = spl[12] self.conflicting = int(spl[13]) self.recording = int(spl[14]) self.override = int(spl[15]) self.hostname = spl[16] self.source_id = int(spl[17]) self.card_id = int(spl[18]) self.input_id = int(spl[19]) self.rec_priority = spl[20] self.rec_status = int(spl[21]) self.record_id = int(spl[22]) self.rec_type = int(spl[23]) self.rec_dups = int(spl[24]) self.rec_start_ts = spl[25] self.rec_end_ts = spl[26] self.repeat = int(spl[27]) self.program_flags = int(spl[28]) self.FileName = self.pathname[7:].split("/")[-1] class filetransfer: # File transfer information, used to gather the important parts of an # active file transfer. def __init__(self, conn, prog): self.err = 0 self.have_stream = 0 self.server = prog.hostname self.port = 6543 self.data = 0 self.id = "" self.start = 0 self.end = 0 class recorder: # Recorder information, used to gather the important parts of an active # Live-TV download session. def __init__(self, r): self.err = 0 self.have_stream = 0 self.id = -1 self.server = "" self.port = 0 self.ring = 0 self.data = 0 self.framerate = 0.0 parts = r.split("[]:[]") if len(parts) != 3: self.err = 1 return self.id = parts[0] if self.id == "-1": self.err = 1 return self.server = parts[1] self.port = int(parts[2]) class ringbuf: # Ring buffer description, part of Live-TV transfer def __init__(self, conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]SETUP_RING_BUFFER[]:[]0" % rec.id) debug("REPLY: '%s'" % r) parts = r.split("[]:[]") self.url = parts[0] self.id = parts[1] self.size = int(parts[2]) self.start = int(parts[3]) self.end = int(parts[4]) class recordernum: def __init__(self, r): self.id = -1 self.host = "" self.port = -1 spl = r.split("[]:[]") if len(spl) == 3: self.id = int(spl[0]) self.host = spl[1] self.port = int(spl[2]) class chaninfo: # Channel information, describes the current channel on a Live-TV # transfer. Includes program descriptive information about the current # show. def __init__(self, raw): spl = raw.split("[]:[]") self.title = spl[0] self.subtitle = spl[1] self.description = spl[2] self.category = spl[3] self.start_ts = spl[4] self.end_ts = spl[5] self.callsign = spl[6] self.iconpath = spl[7] self.channame = spl[8] self.chanid = spl[9] class position: # Position information, used to locate key frames in a LiveTV stream. def __init__(self, keynum, value): self.keynum = long(keynum) self.value = long(value) class freespace: def __init__(self, conn): r = send_request(conn.control, "QUERY_FREESPACE") spl = r.split("[];[]") debug("REPLY: '%s'" % r) self.total = long(spl[0]) self.used = long(spl[1]) # recordernum functions def recordernum_string(num): return "%d[]:[]%s[]:[]%d" % (num.id, num.host, num.port) # Connection functions def connection_shutdown(conn): conn.control.shutdown(2) conn.control.close() def connection_check_block(conn, size): rfds = select.select([conn.control], [], [], 0)[0] if rfds > 0: reply = receive_reply(conn.control) debug("REPLY: '%s' size = %d" % (reply, size)) if reply == str(size): return 1 return 0 def connection_get_recorder_from_num(conn, num): # UNTESTED r = send_request(conn.control, "GET_RECORDER_FROM_NUM %s" % recordernum_string(num)) debug("REPLY: '%s'" % r) rec = recorder(r); if rec.err == 0: rec.ring = ringbuf(conn, rec) return rec # Program Info Functions def programinfo_string_list(prog): ret = str(prog.title) ret = ret + "[]:[]" + str(prog.subtitle) ret = ret + "[]:[]" + str(prog.description) ret = ret + "[]:[]" + str(prog.category) ret = ret + "[]:[]" + str(prog.chanId) ret = ret + "[]:[]" + str(prog.chanstr) ret = ret + "[]:[]" + str(prog.chansign) ret = ret + "[]:[]" + str(prog.channame) ret = ret + "[]:[]" + str(prog.pathname) ret = ret + "[]:[]" + str(prog.Start) ret = ret + "[]:[]" + str(prog.Length) ret = ret + "[]:[]" + str(prog.start_ts) ret = ret + "[]:[]" + str(prog.end_ts) ret = ret + "[]:[]" + str(prog.conflicting) ret = ret + "[]:[]" + str(prog.recording) ret = ret + "[]:[]" + str(prog.override) ret = ret + "[]:[]" + str(prog.hostname) ret = ret + "[]:[]" + str(prog.source_id) ret = ret + "[]:[]" + str(prog.card_id) ret = ret + "[]:[]" + str(prog.input_id) ret = ret + "[]:[]" + str(prog.rec_priority) ret = ret + "[]:[]" + str(prog.rec_status) ret = ret + "[]:[]" + str(prog.record_id) ret = ret + "[]:[]" + str(prog.rec_type) ret = ret + "[]:[]" + str(prog.rec_dups) ret = ret + "[]:[]" + str(prog.rec_start_ts) ret = ret + "[]:[]" + str(prog.rec_end_ts) ret = ret + "[]:[]" + str(prog.repeat) ret = ret + "[]:[]" + str(prog.program_flags) ret = ret + "[]:[]nothing" return ret def programinfo_list(conn): ret = [] s = send_request(conn.control, "QUERY_RECORDINGS Play") debug("REPLY: '%s'" % s) spl = s.split("[]:[]", 1) count = int(spl[0]); while count > 0: s = spl[-1] spl = s.split("[]:[]",29) ret = ret + [programinfo(string.join(spl[0:29], "[]:[]"))] count = count - 1 return ret def programinfo_stop_recording(conn, prog): # UNTESTED r = send_request(conn.control, "STOP_RECORDING %s" % programinfo_string_list(prog)); debug("REPLY: '%s'" % r) def programinfo_check_recording(conn, prog): r = send_request(conn.control, "CHECK_RECORDING %s" % programinfo_string_list(prog)); debug("REPLY: '%s'" % r) return int(r) def programinfo_delete_recording(conn, prog): # UNTESTED r = send_request(conn.control, "DELETE_RECORDING %s" % programinfo_string_list(prog)); debug("REPLY: '%s'" % r) if (r[0:3] == "BAD"): r = "-1" return int(r) def programinfo_forget_recording(conn, prog): # UNTESTED r = send_request(conn.control, "FORGET_RECORDING %s" % programinfo_string_list(prog)); debug("REPLY: '%s'" % r) return int(r) def programinfo_getallpending(conn): # UNTESTED ret = [] s = send_request(conn.control, "QUERY_GETALLPENDING") debug("REPLY: '%s'" % s) spl = s.split("[]:[]", 1) count = int(spl[0]); while count > 0: s = spl[-1] spl = s.split("[]:[]",29) ret = ret + [programinfo(string.join(spl[0:29], "[]:[]"))] count = count - 1 return ret def programinfo_getallscheduled(conn): # UNTESTED ret = [] s = send_request(conn.control, "QUERY_GETALLSCHEDULED") debug("REPLY: '%s'" % s) spl = s.split("[]:[]", 1) count = int(spl[0]); while count > 0: s = spl[-1] spl = s.split("[]:[]",29) ret = ret + [programinfo(string.join(spl[0:29], "[]:[]"))] count = count - 1 return ret def programinfo_getconflicting(conn): # UNTESTED ret = [] s = send_request(conn.control, "QUERY_GETCONFLICTING") debug("REPLY: '%s'" % s) spl = s.split("[]:[]", 1) count = int(spl[0]); while count > 0: s = spl[-1] spl = s.split("[]:[]",29) ret = ret + [programinfo(string.join(spl[0:29], "[]:[]"))] count = count - 1 return ret def programinfo_get_recorder_num(conn, prog): # UNTESTED r = send_request(conn.control, "GET_RECORDER_NUM %s" % programinfo_string_list(prog)); debug("REPLY: '%s'" % r) return recordernum(r) def programinfo_fill_program_info(conn, hostname): # UNTESTED r = send_request(conn.control, "QUERY_GEN_PIXMAP %s" % hostname) debug("REPLY: '%s'" % r) return programinfo(r) # Filetransfer functions def filetransfer_start_stream(conn, ft, prog): ft.data = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ft.data.connect((ft.server, ft.port)) r = send_request(ft.data, "ANN FileTransfer %s[]:[]/%s" % (socket.gethostname(), prog.FileName)) debug("REPLY: '%s'" % r) spl = r.split("[]:[]") if spl[0] != "OK": print "ANN FileTransfer failed" ft.data.close() return -1 ft.id = spl[1] ft.start = long(spl[2]) ft.end = long(spl[3]) ft.have_stream = 1 return 0 def filetransfer_end_stream(conn, ft): reply = send_request(conn.control, "QUERY_FILETRANSFER %s[]:[]DONE" % ft.id) debug("REPLY: '%s'" % reply) reply = send_request(conn.control, "QUERY_IS_ACTIVE_BACKEND[]:[]%s" % socket.gethostname()) debug("REPLY: '%s'" % reply) # All done, close the file and return ft.data.shutdown(2) ft.data.close() ft.have_stream = 0 def filetransfer_request_block(conn, ft, size): send_message(conn.control, "QUERY_FILETRANSFER %s[]:[]REQUEST_BLOCK[]:[]%d" % (ft.id, size)) def checkfile(conn, prog): reply = send_request(conn.control, "QUERY_CHECKFILE[]:[]%s" % programinfo_string_list(prog)) debug("REPLY: '%s'" % reply) return int(reply) == 1 # Recorder functions def recorder_request_block(conn, rec, size): send_message(conn.control, "QUERY_RECORDER %s[]:[]REQUEST_BLOCK_RINGBUF[]:[]%d" % (rec.id, size)) def recorder_is_recording(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]IS_RECORDING" % rec.id) debug("REPLY: %s" % r) return int(r) == 1 def recorder_get_framerate(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_FRAMERATE" % rec.id) debug("REPLY: %s" % r) return float(r) def recorder_get_frames_written(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_FRAMES_WRITTEN" % rec.id) debug("REPLY: %s" % r) spl = r.split("[]:[]") if (int(spl[0]) < 0): return long(-1) return long(spl[1]) def recorder_get_free_space(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_FREE_SPACE" % rec.id) debug("REPLY: %s" % r) spl = r.split("[]:[]") if (int(spl[0]) < 0): return long(-1) return long(spl[1]) def recorder_get_keyframe_pos(conn, rec, keynum): # UNTESTED r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_KEYFRAME_POS[]:[]%d" % (rec.id, keynum)) debug("REPLY: %s" % r) spl = r.split("[]:[]") if (int(spl[0]) < 0): return long(-1) return long(spl[1]) def recorder_fill_position_map(conn, rec, start, end): # UNTESTED r = send_request(conn.control, "QUERY_RECORDER %s[]:[]FILL_POSITION_MAP[]:[]%d[]:[]%d" % (rec.id, start, end)) debug("REPLY: %s" % r) ret = [] if r == "": return ret spl = r.split("[]:[]") while len(spl) > 2: ret = ret + [position(spl[0], spl[1])] spl = spl[2:] return ret def recorder_get_recording(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_RECORDING" % rec.id) debug("REPLY: %s" % r) return programinfo(r) def recorder_stop_playing(conn, rec): # UNTESTED r = send_request(conn.control, "QUERY_RECORDER %s[]:[]STOP_PLAYING" % rec.id) debug("REPLY: %s" % r) def recorder_frontend_ready(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]FRONTEND_READY" % rec.id) debug("REPLY: %s" % r) def recorder_cancel_next_recording(conn, rec): # UNTESTED r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CANCEL_NEXT_RECORDING" % rec.id) debug("REPLY: %s" % r) def recorder_pause(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]PAUSE" % rec.id) debug("REPLY: %s" % r) def recorder_finish_recording(conn, rec): # UNTESTED r = send_request(conn.control, "QUERY_RECORDER %s[]:[]FINISH_RECORDING" % rec.id) debug("REPLY: %s" % r) def recorder_toggle_channel_favorite(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]TOGGLE_CHANNEL_FAVORITE" % rec.id) debug("REPLY: %s" % r) # Channel direction values for Change Channel CHANNEL_DIRECTION_UP = 0 # Change chanel up CHANNEL_DIRECTION_DOWN = 1 # Change channel down CHANNEL_DIRECTION_FAVORITE = 3 # Flip between favorite channels CHANNEL_DIRECTION_SAME = 4 # Stay where you are (not used, treated as down) def recorder_change_channel(conn, rec, direction): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHANGE_CHANNEL[]:[]%d" % (rec.id, direction)) debug("REPLY: %s" % r) def recorder_set_channel(conn, rec, channame): # Change channels absolutely (by name) r = send_request(conn.control, "QUERY_RECORDER %s[]:[]SET_CHANNEL[]:[]%s" % (rec.id, channame)) debug("REPLY: %s" % r) # Adjustment directions for change {color, brightness, contrast, hue} UP = 1 # Adjust it up one notch DOWN = 0 # Adjust it down one notch def recorder_change_color(conn, rec, direction): # Adjust color saturation up [1] or down [0] r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHANGE_COLOUR[]:[]%d" % (rec.id, direction)) debug("REPLY: %s" % r) return int(r) def recorder_change_brightness(conn, rec, direction): # Adjust brightness (black level) up [1] or down [0] r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHANGE_BRIGHTNESS[]:[]%d" % (rec.id, direction)) debug("REPLY: %s" % r) return int(r) def recorder_change_contrast(conn, rec, direction): # Adjust contrast (white level) up [1] or down [0] r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHANGE_CONTRAST[]:[]%d" % (rec.id, direction)) debug("REPLY: %s" % r) return int(r) def recorder_change_hue(conn, rec, direction): # Adjust hue up [1] or down [0] r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHANGE_HUE[]:[]%d" % (rec.id, direction)) debug("REPLY: %s" % r) return int(r) def recorder_check_channel(conn, rec, channame): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHECK_CHANNEL[]:[]%s" % (rec.id, channame)) debug("REPLY: %s" % r) return int(r) == 1 def recorder_check_channel_prefix(conn, rec, channame): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]CHECK_CHANNEL_PREFIX[]:[]%s" % (rec.id, channame)) debug("REPLY: %s" % r) spl = r.split("[]:[]") if int(spl[0]) < 0: return -1 return int(spl[1]) == 1 # Direction values for Get Next Program BROWSE_SAME = 0 # Stay where you are BROWSE_UP = 1 # Channel up, same time slot BROWSE_DOWN = 2 # Channel down, same time slot BROWSE_LEFT = 3 # Channel same, earlier time slot BROWSE_RIGHT = 4 # Channel same, later time slot BROWSE_FAVORITE = 5 # Not quite sure, I think this flips # between tagged favorites def recorder_get_next_program_info(conn, rec, cinfo, direction): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_NEXT_PROGRAM_INFO[]:[]%s[]:[]%s[]:[]%s[]:[]%s" % (rec.id, cinfo.channame, cinfo.chanid, direction, cinfo.start_ts)) debug("REPLY: %s" % r) return chaninfo(r) def recorder_get_program_info(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_PROGRAM_INFO" % rec.id) debug("REPLY: %s" % r) return chaninfo(r) def recorder_get_input_name(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]GET_INPUT_NAME" % rec.id) debug("REPLY: %s" % r) return r def recorder_seek(conn, rec, pos, whence, curpos): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]SEEK_RINGBUF[]:[]%ld[]:[]0[]:[]%ld[]:[]%ld[]:[]0" % (rec.id, pos, whence, curpos)) debug("REPLY: %s" % r) return long(r.split("[]:[]")[0]) def recorder_spawn_livetv(conn, rec): r = send_request(conn.control, "QUERY_RECORDER %s[]:[]SPAWN_LIVETV" % rec.id) debug("REPLY: '%s'" % r) if r != "ok": return -1 return 0 def recorder_get_free_recorder(conn): r = send_request(conn.control, "GET_FREE_RECORDER") debug("REPLY: '%s'" % r) rec = recorder(r); rec.ring = ringbuf(conn, rec) return rec def recorder_start_stream(conn, rec): rec.data = socket.socket(socket.AF_INET, socket.SOCK_STREAM) rec.data.connect((rec.server, rec.port)) r = send_request(rec.data, "ANN RingBuffer %s %s" % (socket.gethostname(), rec.id)) debug("REPLY: '%s'" % r) if r != "OK": print "Announce recorder failed" rec.data.close() return -1 if recorder_spawn_livetv(conn, rec) < 0: print "SPAWN LIVETV Failed" rec.data.close() return -1 if recorder_is_recording(conn, rec) == 0: print "Not recording" rec.data.close() return -1 rec.frame_rate = recorder_get_framerate(conn, rec) rec.have_stream = 1 return 0 def recorder_end_stream(conn, rec): reply = send_request(conn.control, "QUERY_RECORDER %s[]:[]STOP_LIVETV" % rec.id) debug("REPLY: '%s'" % reply) reply = send_request(conn.control, "QUERY_RECORDER %s[]:[]DONE_RINGBUF" % rec.id) debug("REPLY: '%s'" % reply) reply = send_request(conn.control, "QUERY_IS_ACTIVE_BACKEND[]:[]%s" % socket.gethostname()) debug("REPLY: '%s'" % reply) rec.data.shutdown(2) rec.data.close() rec.have_stream = 0 rec.frame_rate = 0.0 # Transfer Functions (file and live-tv) def transfer_file(conn, prog): ft = filetransfer(conn, prog) if filetransfer_start_stream(conn, ft, prog) < 0: return out = file("/tmp/" + prog.FileName, "w+b") remaining = long(prog.Length) block = 256000; while remaining > 0: count = 0 if (remaining < block): block = remaining # Ask for a block of data filetransfer_request_block(conn, ft, block) # Now read down the block of data a chunk at a time while count < block: reply = ft.data.recv(block - count) length = len(reply) count = count + length remaining = remaining - length if length > 0: # If we got anything, write it out, otherwise, just go # back for more. out.write(reply) # Read back a block end indicator, it should be '13 256000' if connection_check_block(conn, block) == 0: print "SHOULD BE BLOCK END BUT IS NOT!!!" # All done filetransfer_end_stream(conn, ft) out.close() def transfer_live(conn, length): rec = recorder_get_free_recorder(conn) if rec.err: print "error getting recorder" return if recorder_start_stream(conn, rec) < 0: return prog = recorder_get_program_info(conn, rec) print "Channel %s: %s -- %s" % ( prog.channame, prog.title, prog.description) out = file("/tmp/recording.mpg", "w+b") remaining = long(length) block = 256000; recorder_seek(conn, rec, 0L, 0L, 0L) while remaining > 0: # Ask for a block of data count = 0 if (remaining < block): block = remaining recorder_request_block(conn, rec, block) # Now read down the block of data a chunk at a time while count < block: reply = rec.data.recv(block - count) length = len(reply) count = count + length remaining = remaining - length if length > 0: # If we got anything, write it out, # otherwise, just go back for more. out.write(reply) if connection_check_block(conn, block) == 0: print "SHOULD BE AT BLOCK END BUT NOT!!!" # All done... recorder_end_stream(conn, rec) out.close() def main(): # Set up a connection to the main backend server (mediaserve is my backend # server, substitute your own backed server name here) conn = connection("mediaserve", 6543) # Get and display a list of recorded programs list = programinfo_list(conn) for p in list: print ("%s: %s [%s] - %s" % (p.channame, p.title, p.description, p.start_ts)) # If the list contained at least one recording, transfer it here if len(list) > 0: transfer_file(conn, list[0]) # Grab a short sample of live tv (1 megabyte) transfer_live(conn, 1024*1024) # terminate the connection with the backend server connection_shutdown(conn) # Run Main main()