def recibeVideo(self): connection = Application.getInstance().getConnection() while not self.closedInterface.is_set() and connection != None: start = time.time() frameTime = 1/self.fpsOutput if connection.isRunning(): try: msg = connection.rcv() frame = msg['image'] self.fpsInput = msg['fps'] self.inputResolution = msg['resolution'] img_tk = ImageTk.PhotoImage(frame) # Lo mostramos en el GUI self.app.setImageData('receivedVideoImage', img_tk, fmt = 'PhotoImage') self.app.setImageWidth('receivedVideoImage', self.inputResolution[0]) self.app.setImageHeight('receivedVideoImage', self.inputResolution[1]) self.app.setStatusbar('Recibiendo a {}x{}/{} fps.'.format(self.inputResolution[0], self.inputResolution[1], msg['fps']), field=0) self.app.setStatusbar('Retraso de {:+.3f} segundos.'.format(time.time() - msg['timestamp']), field=1) self.app.setStatusbar('Duracion de la llamada: {} segundos'.format(utils.timedeltaToStr(time.time() - self.timeCallStarted)), field=2) except BlockingIOError as e: # No recibimos datos self.app.setImage('receivedVideoImage', 'imgs/user_icon.gif') except TypeError as e: # timeCallStarted es None por haber sido cambiado en otro hilo al cerrarse la llamada pass # Pausamos el tiempo necesario para cuadrar los fps pauseTime = frameTime + start - time.time() # frameTime - (time.time() - start) if pauseTime < 0: continue time.sleep(pauseTime) connection = Application.getInstance().getConnection()
def login(self, button): # Nos registramos / logeamos en el servidor de descubrimiento password = self.app.getEntry('loginPasswordEntry') if not password: self.notifyLogin('Es necesario que introduzcas una contraseña.') self.notifyLogin('Consultando información con el servidor.') # Deshabilitamos los botones mientras consultamos en el servidor para evitar # posibles errores al pulsar varias veces self.app.queueFunction(self.app.disableButton, 'Register user') self.app.queueFunction(self.app.disableButton, 'Login') user = getUserInfo() if user == None: self.notifyLogin('Ha habido un error, el usuario no esta almacenado. Es necesario que se registre') self.app.queueFunction(self.app.hideLabelFrame, 'Login') return tmp = identityGestion.registerUser(user.nick, user.ip, user.port, password, '#'.join(user.protocols)) if tmp != None: # La contraseña es correcta, acabamos Application.getInstance().setUser(tmp) Application.getInstance().startControlConnection(tmp.ip, tmp.port) # La conexión de control se encarga de mostrar o no la pantalla principal else: # Volvemos a habilitar los botones self.app.queueFunction(self.app.enableButton, 'Register user') self.app.queueFunction(self.app.enableButton, 'Login') self.notifyLogin('La contraseña es incorrecta o el usuario ya no existe.')
def stop(self, signal=None, _=None): # Al hacer sys.exit(0), es posible que el manejador de Ctrl+C llame a la funcion # En ese caso, ya se habrá cerrado todo. Así evitamos fallos. if self.closedInterface.is_set(): return True print('Cerramos la aplicacion') self.closedInterface.set() try: # Dejamos tiempo para que los hilos de mostrar video acaben self.notify('Cerrando la aplicación.') time.sleep(1) # Liberamos el archivo de video usado if not self.usingCamera.is_set(): self.cap.release() connection = Application.getInstance().getConnection() if connection != None: user = connection.dstUser # Colgamos la llamada con el usuario self.notify('Finalizando la llamada.') Application.getInstance().getControlConnection().endCall() self.closeCallWindow() self.notify('Cerrando la conexión de control.') Application.getInstance().getControlConnection().quit() self.app.queueFunction(sys.exit, 0) return True except Exception as e: self.logger.exception('VideoClient - Error al cerrar: '+str(e)) self.app.queueFunction(sys.exit, 0)
def buttonsCallback(self, button): self.logger.debug('VideoClient - Pressed {} button.'.format(button)) if button == 'Salir': # Salimos de la aplicación self.app.thread(self.stop) if button == 'Colgar': # Avisamos a la conexion de control, que se encarga automáticamente de cerrar la conexion # Como el proceso de detener la conexion puede tardar, lo hacemos en otro hilo self.app.thread(Application.getInstance().getControlConnection().endCall) self.closeCallWindow() self.app.enableButton('Conectar') self.app.disableButton('Colgar') elif button == 'Conectar': self.app.thread(self.populateSearchNickWindow) self.app.showSubWindow('searchNickWindow') elif button == 'Aceptar': Application.getInstance().getControlConnection().answerRequest('CALL_ACCEPTED') elif button == 'Rechazar': Application.getInstance().getControlConnection().answerRequest('CALL_DENIED') self.app.hideSubWindow('videoWindow') elif button == 'Cargar video': if self.usingCamera.is_set(): path = self.app.openBox(title='Load video', fileTypes=[('video', '.mp4'), ('video', '.avi')], asFile=False, parent=None) if isinstance(path, str): # Si no se selecciona un archivo, devuelve una tupla self.app.thread(self.capturaVideoArchivo, path) else: # Al hacer set de usingCamera, la funcion de mandar el archivo termina e inicia la de la webcam self.usingCamera.set() elif button == 'pauseResumeButton': connection = Application.getInstance().getConnection() if connection == None: self.logger.error('VideoClient - Connection is none on Pause/Resume button click.') return if connection.isRunning(): self.logger.debug('VideoClient - Paused connection with user {}'.format(connection.dstUser)) self.app.queueFunction(Application.getInstance().getControlConnection().holdCall) self.setPauseResumeButton('Reanudar') elif connection.isPaused(): self.logger.debug('VideoClient - Resumed connection with user {}'.format(connection.dstUser)) self.app.queueFunction(Application.getInstance().getControlConnection().resumeCall) self.setPauseResumeButton('Pausar') elif button == 'filtersOptionBox': self.imageFilter = filters.FILTERS[self.app.getOptionBox('filtersOptionBox')]
def populateSearchNickWindow(self): self.logger.debug('VideoClient - Populating search nick window.') users = identityGestion.listUsers() users = list(map(lambda u: u.nick, users)) # Eliminamos nuestro usuario de la lista nick = Application.getInstance().getUser().nick if nick in users: users.remove(nick) self.app.queueFunction(self.app.updateListBox, 'usersListBox', users)
def endCall(self): origUser = Application.getInstance().getUser() if origUser == None: self.logger.error( 'ControlConnection - Usuario origen no loggeado.') return connection = Application.getInstance().getConnection() if connection == None: self.logger.error( 'ControlConnection - Connection is none on call end.') return dstUser = connection.dstUser message = 'CALL_END {}'.format(origUser.nick) response = utils.sendTCPMessageOnly(message, dstUser.ip, dstUser.port) # Cerramos la conexion connection.quit() Application.getInstance().setConnection(None)
def showControlConnectionSuccess(self): self.correctlyLogged.set() user = Application.getInstance().getUser() if user == None: self.logger.error('Una vez iniciada la conexion de control User is None') return self.notify('Logeado correctamente como '+str(user.nick)) self.app.queueFunction(self.app.hideSubWindow, 'loginWindow') # Iniciamos la captura de video una vez estamos logeados. self.app.thread(self.capturaVideo) self.app.queueFunction(self.app.show)
def resumeCall(self): origUser = Application.getInstance().getUser() if origUser == None: self.logger.error( 'ControlConnection - Usuario origen no loggeado.') return connection = Application.getInstance().getConnection() if connection == None: self.logger.error( 'ControlConnection - Connection is none on call resume.') return dstUser = connection.dstUser message = 'CALL_RESUME {}'.format(origUser.nick) response = utils.sendTCPMessageOnly(message, dstUser.ip, dstUser.port) # Reanudamos la conexion connection = Application.getInstance().getConnection() if connection != None: connection.resume()
def connectTo(self, nick, entry): if nick == []: # Al iniciarse por primera vez return if entry == 'ListBox': nick = nick[0] self.app.queueFunction(self.app.hideSubWindow, 'searchNickWindow') self.notify('Buscando al usuario en el servidor') user = identityGestion.quertyUser(nick) if user == None: self.notify('Error al obtener los datos del usuario {}'.format(nick)) else: # Comenzamos la llamada self.notify('Llamando al usuario {}'.format(user)) # Deshabilitamos el boton de conectar hasta que se haya finalizado la llamada self.app.queueFunction(self.app.disableButton, 'Conectar') self.app.queueFunction(self.app.enableButton, 'Colgar') Application.getInstance().getControlConnection().connectWith(user)
def register(self, button): # Nos registramos / logeamos en el servidor de descubrimiento nick = self.app.getEntry('registerNickEntry') if not nick: self.notifyLogin('Es necesario que introduzcas un nick.') return password = self.app.getEntry('registerPasswordEntry') if not password: self.notifyLogin('Es necesario que introduzcas una contraseña.') return ip = self.app.getEntry('registerIPEntry') if not ip or not utils.isIPv4(ip): self.notifyLogin('Es necesario que introduzcas una IPv4 válida.') return port = self.app.getEntry('registerPortEntry') if not port or not utils.isInteger(port) or int(port)<0 or int(port)>65535: self.notifyLogin('Es necesario que introduzcas un puerto válido.') return self.notifyLogin('Registrandote en el servidor.') # Deshabilitamos los botones mientras consultamos en el servidor para evitar # posibles errores al pulsar varias veces self.app.queueFunction(self.app.disableButton, 'Register user') self.app.queueFunction(self.app.disableButton, 'Login') tmp = identityGestion.registerUser(nick, ip, int(port), password, '#'.join(Application.protocols)) if tmp != None: # La contraseña es correcta, acabamos saveUserInfo(tmp) Application.getInstance().setUser(tmp) Application.getInstance().startControlConnection(tmp.ip, tmp.port) # La conexión de control se encarga de mostrar o no la pantalla principal else: # Volvemos a habilitar los botones self.app.queueFunction(self.app.enableButton, 'Register user') self.app.queueFunction(self.app.enableButton, 'Login') self.notifyLogin('Ya hay un usuario con este nick.')
def enviarFrame(self, frame, hres, vres, fps): connection = Application.getInstance().getConnection() if connection != None and connection.isRunning(): encode_param = [cv2.IMWRITE_JPEG_QUALITY,50] #50% calidad. Cambiar mas adelante result,encimg = cv2.imencode('.jpg', frame, encode_param) if result == False: self.logger.error('VideoClient - Error al codificar la imagen a enviar') return imgData = encimg.tobytes() # Codificamos la imagen data = '{}#{}#{}#{}#'.format(str(self.sentFrames), str(time.time()), '{}x{}'.format(hres, vres), str(fps)) data = data.encode()+imgData connection.send(data) self.sentFrames += 1
def closeCallWindow(self): self.logger.debug('VideoClient - Connection stopped.') self.sentFrames = 0 self.timeCallStarted = None self.app.queueFunction(self.app.hideSubWindow, 'videoWindow') self.app.queueFunction(self.app.enableButton, 'Conectar') self.app.queueFunction(self.app.disableButton, 'Colgar') # Reseteamos la imagen de llamada entrante para futuras llamadas self.app.queueFunction(self.app.setImage, 'receivedVideoImage', 'imgs/user_icon.gif') # Reseteamos los campos de la statusbar self.app.queueFunction(self.app.setStatusbar, '', field=0) self.app.queueFunction(self.app.setStatusbar, '', field=1) self.app.queueFunction(self.app.setStatusbar, '', field=2) self.notify('Logeado correctamente como '+str(Application.getInstance().getUser().nick))
def start(self): # Creamos el socket self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Lo asociamos a la IP y puerto correctos try: self.server.bind((self.ip, self.port)) except Exception as e: print('{}:{}'.format(self.ip, self.port)) self.logger.info( 'ControlConnection - El puerto o la dirección IP de la conexion de control no son válidos.' ) Application.getInstance().getInterface().notify( 'Error al iniciar la conexión de control en la ip y puerto especificados.' ) Application.getInstance().getInterface( ).showControlConnectionError() return Application.getInstance().getInterface().showControlConnectionSuccess() # Indicamos el numero de conexiones self.server.listen(self.maxConnections) self.logger.debug( 'ControlConnection - Conexión de control inicializada en {}:{}'. format(self.ip, self.port)) self.server.settimeout(1) # Establecemos el Event self.runningEvent.set() while self.runningEvent.is_set(): try: client_sock, address = self.server.accept() self.logger.debug( 'ControlConnection - Aceptada conexion de {}:{}.'.format( address[0], address[1])) # Un único para así saber siempre quien es el usuario que nos llama, por ejemplo self.conexionHandler(client_sock) except socket.error as e: continue
def startCallV1(self, nick, ip, port): ports = utils.getAvailablePort(self.ip, nports=2) origVideoPort, origAudioPort = ports[0], ports[1] origUser = Application.getInstance().getUser() if origUser == None: self.logger.error( 'ControlConnection - Usuario origen no loggeado.') return message = 'CALLING_V1 {} {} {}'.format(origUser.nick, origVideoPort, origAudioPort) response = utils.sendTCPMessage(message, ip, port, timeout=self.waitCallTimeout) if response == None: self.logger.error( 'ControlConnection - Respuesta no recibida al contactar: {}-{}:{} para llamada con audio.' .format(nick, ip, port)) Application.getInstance().getInterface().closeCallWindow() Application.getInstance().getInterface().notify( 'El usuario {} no está disponible ahora mismo.'.format(nick)) return response = response.split(' ') self.logger.debug('ControlConnection - {} \n\tGot response: {}'.format( message, response)) if response[0] == 'CALL_ACCEPTED_V1': nick = response[1] dstVideoPort = int(response[2]) dstAudioPort = int(response[3]) user = identityGestion.quertyUser(nick) if user == None: self.logger.error( 'ControlConnection - Usuario {} no encontrado. No se puede aceptar la llamada con audio entrante.' .format(nick)) return conn = udp.VideoAudioCallConnection(self.ip, user.ip, origVideoPort, dstVideoPort, origAudioPort, dstAudioPort, user, self.logger) if conn.start(): Application.getInstance().setConnection(conn) Application.getInstance().getInterface().startCallWindow(user) Application.getInstance().getInterface().notify( 'Comenzando la llamada con audio con el usuario {}'.format( nick)) else: Application.getInstance().getInterface().notify( 'Error al comenzar la llamada con audio con el usuario {}'. format(nick)) elif response[0] == 'CALL_DENIED': nick = response[1] self.logger.debug( 'ControlConnection - El usuario {} no encontrado ha denegado la llamada con audio.' .format(nick)) Application.getInstance().getInterface().closeCallWindow() Application.getInstance().getInterface().notify( 'El usuario {} ha colgado.'.format(nick)) elif response[0] == 'CALL_BUSY': self.logger.debug( 'ControlConnection - El usuario {} está ocupado para una llamada con audio.' .format(nick)) Application.getInstance().getInterface().closeCallWindow() Application.getInstance().getInterface().notify( 'El usuario {} está ocupado en estos momentos.'.format(nick)) else: self.logger.error( 'ControlConnection - Respuesta incorrecta a llamada con audio: {}.' .format(response))
def conexionHandler(self, socket): try: request = socket.recv(1024).decode() self.logger.debug( 'ControlConnection - Socket {}. Recibido: {}'.format( socket.getpeername(), request)) request = request.split(' ') # Recibimos una llamada entrante if request[0] == 'CALLING': nick = request[1] dstUDPport = int(request[2]) user = identityGestion.quertyUser(nick) if user == None: self.logger.error( 'ControlConnection - Usuario {} no encontrado. No se puede empezar la llamada.' .format(nick)) return # Si ya estamos en una llamada, avisamos de que estamos ocupados y notificamos por la interfaz if Application.getInstance().getConnection() != None: socket.send('CALL_BUSY {}'.format( Application.getInstance().getUser().nick).encode()) Application.getInstance().getInterface().notify( 'Llamada recibida del usuario {}'.format(nick)) Application.getInstance().getInterface().showIncomingCall( user, True) # Tenemos que esperar a que el usuario pulse el botón self.waitEvent.clear() self.waitEvent.wait(timeout=self.waitCallTimeout) if not self.waitEvent.is_set(): # El usuario no ha respondido a la llamada Application.getInstance().getInterface().closeCallWindow() Application.getInstance().getInterface().notify( 'Llamada perdida del usuario {}'.format(nick)) # El usuario decice aceptar la llamada if self.interfaceResponse == 'CALL_ACCEPTED': # Iniciamos la conexion UDP origPort = utils.getAvailablePort(self.ip) socket.send('CALL_ACCEPTED {} {}'.format( Application.getInstance().getUser().nick, origPort).encode()) conn = udp.VideoConnection(self.ip, user.ip, origPort, dstUDPport, user, self.logger) if conn.start(): Application.getInstance().setConnection(conn) Application.getInstance().getInterface( ).startCallWindow(user) Application.getInstance().getInterface().notify( 'Comenzando la llamada con el usuario {}'.format( nick)) else: Application.getInstance().getInterface().notify( 'Error al comenzar la llamada con el usuario {}'. format(nick)) # El usuario decice rechazar la llamada elif self.interfaceResponse == 'CALL_DENIED': # Colgamos la llamada socket.send('CALL_DENIED {}'.format( Application.getInstance().getUser().nick).encode()) # El usuario indica que esta ocupado - De momento no se puede, no le vemos sentido elif self.interfaceResponse == 'CALL_BUSY': socket.send('CALL_BUSY {}'.format( Application.getInstance().getUser().nick).encode()) else: self.logger.error( 'ControlConnection - Respuesta incorrecta por parte del usuario a CALLING: {}' .format(self.interfaceResponse)) # Recibimos una llamada con audio entrante if request[0] == 'CALLING_V1': nick = request[1] dstVideoPort = int(request[2]) dstAudioPort = int(request[3]) user = identityGestion.quertyUser(nick) if user == None: self.logger.error( 'ControlConnection - Usuario {} no encontrado. No se puede empezar la llamada.' .format(nick)) return # Si ya estamos en una llamada, avisamos de que estamos ocupados y notificamos por la interfaz if Application.getInstance().getConnection() != None: socket.send('CALL_BUSY {}'.format( Application.getInstance().getUser().nick).encode()) Application.getInstance().getInterface().notify( 'Llamada recibida del usuario {}'.format(nick)) Application.getInstance().getInterface().showIncomingCall( user, True) # Tenemos que esperar a que el usuario pulse el botón self.waitEvent.clear() self.waitEvent.wait(timeout=self.waitCallTimeout) if not self.waitEvent.is_set(): # El usuario no ha respondido a la llamada Application.getInstance().getInterface().closeCallWindow() Application.getInstance().getInterface().notify( 'Llamada perdida del usuario {}'.format(nick)) # El usuario decice aceptar la llamada if self.interfaceResponse == 'CALL_ACCEPTED': # Iniciamos la conexion UDP ports = utils.getAvailablePort(self.ip, nports=2) origVideoPort, origAudioPort = ports[0], ports[1] socket.send('CALL_ACCEPTED_V1 {} {} {}'.format( Application.getInstance().getUser().nick, origVideoPort, origAudioPort).encode()) conn = udp.VideoAudioCallConnection( self.ip, user.ip, origVideoPort, dstVideoPort, origAudioPort, dstAudioPort, user, self.logger) if conn.start(): Application.getInstance().setConnection(conn) Application.getInstance().getInterface( ).startCallWindow(user) Application.getInstance().getInterface().notify( 'Comenzando la llamada con el usuario {}'.format( nick)) else: Application.getInstance().getInterface().notify( 'Error al comenzar la llamada con el usuario {}'. format(nick)) # El usuario decice rechazar la llamada elif self.interfaceResponse == 'CALL_DENIED': # Colgamos la llamada socket.send('CALL_DENIED {}'.format( Application.getInstance().getUser().nick).encode()) # El usuario indica que esta ocupado - De momento no se puede, no le vemos sentido elif self.interfaceResponse == 'CALL_BUSY': socket.send('CALL_BUSY {}'.format( Application.getInstance().getUser().nick).encode()) else: self.logger.error( 'ControlConnection - Respuesta incorrecta por parte del usuario a CALLING: {}' .format(self.interfaceResponse)) # Si es una peticion sobre una conexion ya iniciada else: connection = Application.getInstance().getConnection() if connection == None: self.logger.error( 'ControlConnection - Recibido: {} cuando no habia conexion UDP iniciada' .format(request)) return nick = request[1] if connection.dstUser.nick != nick: self.logger.error( 'ControlConnection - El usuario {} intenta infiltrarse en nuestra conexion con {}' .format(nick, connection.dstUser.nick)) return # Recibimos una peticion para pausar la llamada if request[0] == 'CALL_HOLD': # Pausamos la conexion connection.pause() Application.getInstance().getInterface( ).setPauseResumeButton('Reanudar') Application.getInstance().getInterface().notify( 'El usuario {} ha pausado la conexion.'.format(nick)) # Recibimos una peticion para reanudar la llamada elif request[0] == 'CALL_RESUME': # Reanudamos la conexion connection.resume() Application.getInstance().getInterface( ).setPauseResumeButton('Pausar') Application.getInstance().getInterface().notify( 'El usuario {} ha reanudado la conexion.'.format(nick)) # Recibimos una peticion para detener definitivamente la llamada elif request[0] == 'CALL_END': # Eliminamos definitivamente la conexion Application.getInstance().getInterface().closeCallWindow() Application.getInstance().setConnection(None) connection.quit() finally: socket.close()
import utils from user import getUserInfo # # Inicializa el logger, la conexión de control, la interfaz y el Application en sí. # Lanza el Application. # if __name__ == '__main__': # Iniciamos un logger para obtener informacion ante posibles fallos logger = logging.getLogger(utils.NAME) logger.setLevel(logging.DEBUG) formatter = logging.Formatter( fmt='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') handler = RotatingFileHandler(utils.LOG_FILE, maxBytes=256000, backupCount=10) handler.setFormatter(formatter) handler.setLevel(logging.DEBUG) logger.addHandler(handler) vc = VideoClient(utils.WIN_SIZE, logger) cc = ControlConnection(10, logger) app = Application.getInstance(None, vc, cc, logger) # Inicializa la aplicación. Es bloqueante app.start() logging.shutdown()