#!/usr/bin/env python2
# mplayer syncronize v.0.02 alpha --
#
# MPlayer has native support for udp syncronization used to syncronize
# video walls etc in local networks. (-udp-ip/-udp-master/-udp-slave)
# It sends a datagram for each frame to syncronize but does not solve
# the latency issues that occur over the internet. This script is
# planned to do exactly that.
#
# TODO: the server should coordinate a real syncronization. probably the best
# way to do this would be to use the local system time of each client.
# (assuming it is syncronized by ntp)
# BUGS: sometimes the sockets are not properly closed. should rewrite most of
# the the threading/socket uninitialization code.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
import re, sys, time, subprocess, threading, socket
if len(sys.argv) < 4:
print('Usage: %s [-server] ' % sys.argv[0])
print('\nhost, port -- used for server and client mode (tcp)')
print('mediafile -- local mediafile (all clients should use the same)')
print('options -- gets passed to the mplayer process example: "-fs -display :0.0"')
sys.exit()
# settings
host = port = mediafile = None
server_mode = False
options = ''
socket_timeout = 3
# parse arguments
n = 1
if sys.argv[n] == '-server': server_mode = True; n+=1
host = sys.argv[n]
port = int(sys.argv[n+1])
mediafile = sys.argv[n+2]
if len(sys.argv)-n == 4: options = sys.argv[n+3]
# Client thread (used for client and server)
class Client(threading.Thread):
def __init__(self, socket, **kwargs):
super(Client, self).__init__()
self.socket = socket
self.socket.settimeout(socket_timeout)
self.server = None
self.mplayer = None
if 'server' in kwargs: self.server = kwargs['server']
if 'mplayer' in kwargs: self.mplayer = kwargs['mplayer']
def run(self):
buf = ''
self.running = True
while self.running:
# read line from socket:
if buf == '':
raw = None
try:
raw = self.socket.recv(1024)
except:
pass
if not raw: continue
buf += raw
nl = buf.index('\n')
line = buf[0:nl+1].strip()
buf = buf[nl+1:]
if line == '': continue
# process received line:
print('[Client: %s] "%s"' % (self.socket.getpeername()[0], line))
# client quit command
if line == 'cquit': break
# parse lines and execute mplayer commands
# or just forward them:
if self.server:
for client in self.server.clients:
client.send(line) # forwarding
elif self.mplayer:
arg = ''
si = len(line)
if ' ' in line:
si = line.index(' ')
arg = line[si+1:]
command = line[0:si]
if command == 'pause' or command == 'p':
self.mplayer.pause()
elif command == 'osd':
self.mplayer.osd(arg)
elif command == 'seek':
self.mplayer.seek(arg)
elif command == 'raw':
self.mplayer.send(arg)
self.socket.close()
self.running = False
print('[Client] disconnected')
if self.server:
self.server.clients.remove(self)
def send(self, cmd):
self.socket.send(cmd+'\n')
def quit(self):
self.send('cquit')
self.running = False
class Server(threading.Thread):
def __init__(self):
super(Server, self).__init__()
self.running = False
def run(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.clients = []
self.socket.bind((host, port))
self.socket.listen(1)
self.socket.settimeout(socket_timeout)
self.running = True
print('[Server] Wait for clients')
while self.running:
try:
client_socket, client_addr = self.socket.accept()
print('[Server] Accept client: %s' % str(client_addr))
client = Client(client_socket, server=self)
client.daemon = True
client.start()
self.clients.append(client)
except:
pass
self.socket.close()
def wait_for_it(self):
while not self.running:
pass
def quit(self):
print('Close server')
self.socket.close()
for client in self.clients:
client.quit()
self.running = False
class MplayerProcess(threading.Thread):
cmd = 'mplayer -slave -quiet -input nodefault-bindings:conf=/dev/null %s "%s"'
#cmd = 'mplayer -slave -quiet -input nodefault-bindings -noconfig all %s "%s"'
def __init__(self, mediafile, args=''):
super(MplayerProcess, self).__init__()
self.mediafile = mediafile
self.args = args
self.proc = None
def run(self):
self.running = True
cmd = MplayerProcess.cmd % (self.args, self.mediafile)
print('Start mplayer: %s' % cmd)
self.proc = subprocess.Popen(cmd,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, shell=True, close_fds=True)
self.ret_code = self.proc.wait()
def wait_for_it(self):
while self.is_alive() and not self.proc:
pass
if not self.is_alive(): return False
while not 'Starting playback...' in self.proc.stdout.readline():
pass
return True
def send(self, cmd):
self.proc.stdin.write('%s\n' % cmd)
def pause(self):
self.send('pause')
def quit(self):
print('Send quit command to mplayer...')
self.running = False
self.send('quit')
def osd(self, msg):
print('Receive osd message: %s' % msg)
self.send('pausing_keep osd_show_text %s' % msg)
def seek(self, sec):
self.send('pausing seek %d 2' % int(sec))
def fs(self):
print('switch to fullscreen mode')
self.send('pausing_keep vo_fullscreen')
mplayer = MplayerProcess(mediafile, options)
mplayer.start()
if not mplayer.wait_for_it():
print('meh.')
sys.exit()
mplayer.pause()
print('MPlayer is running. The default keybindings of mplayer are')
print('deactivated. Please use this interface to control it:')
print('\n p, pause -- toggle pause everywhere (remote)')
print(' seek [x] -- seek to x seconds (remote)')
print(' raw [x] -- execute x via mplayers slave mode protocol:')
print(' http://www.mplayerhq.hu/DOCS/tech/slave.txt')
print(' fs -- toggle fullscreen mode (local)')
print(' q, quit -- clean exit (local)\n')
if server_mode:
server = Server()
server.daemon = True
server.start()
server.wait_for_it() # TODO: moo..
else:
server = None
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
client_socket.connect((host, port))
except:
print('Client Socket Error! (Server connection failed?)')
mplayer.quit()
sys.exit()
client = Client(client_socket, mplayer=mplayer) # the client controls the mplayer
client.daemon = True
client.start()
while mplayer.running:
cmd = raw_input('> ')
if 'fs' in cmd:
mplayer.fs()
continue
if 'quit' in cmd or cmd == 'q':
mplayer.quit()
if server:
server.quit()
client.quit()
break
# other commands should be sent via client to the server
client.send(cmd.strip())
if server: server.quit()
client.quit()