#!/usr/bin/python3 import argparse import datetime import os import random import socket import string import subprocess import sys import time from glob import glob from queue import Queue from socketserver import UDPServer, ThreadingMixIn, BaseRequestHandler from threading import Thread import ox import mpv import logging logger = logging.getLogger('cdosea') DEFAULT_PORT = 2680 DEBUG = False def update_playlist(playlist, prefix='video/', position=None): playlist = 'play.m3u' files = [] videos = {} for letter in string.ascii_uppercase: videos[letter] = glob('%s%s*.mp4' % (prefix, letter.lower())) random.shuffle(videos[letter]) for i in range(10): for letter in string.ascii_uppercase: files.append(videos[letter][i]) if position is None: today = datetime.date.today() seconds_since_midnight = time.time() - time.mktime(today.timetuple()) offset = 0 position = 0 while offset < seconds_since_midnight: f = files.pop(0) try: offset += ox.avinfo(f)['duration'] files.append(f) except: pass position += 1 else: pos = position while pos: f = files.pop(0) files.append(f) pos -= 1 with open(playlist, 'w') as f: f.write('\n'.join(files)) f.write('\n') return position, len(files) def trigger_lights(path): #number = int(path.split('/')[-1].split('.')[0][1:]) #cmd = ['/opt/LanBox-JSONRPC/fade.py', str(number)] cmd = ['./lights.py', path] subprocess.Popen(cmd) class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass def q_binding(self, *args): self.player.running = False self.shutdown() class Handler(BaseRequestHandler): def handle(self): data = self.request[0].strip() logger.debug("%s said: %s", self.client_address[0], data) queue = False if data == b'POS': response = ('%d' % self.server.player.position).encode() else: if self.server.player.playing: response = b'PLAYING' else: response = b'WAITING' queue = True socket = self.request[1] socket.sendto(response, self.client_address) if queue: self.server.player.queue.put('') class Player(Thread): first = True running = True playing = False current = '' playlist_items = 260 def __init__(self, peer, player, playlist, lights=False): self.peer = peer self.playlist = playlist self.player = player self.lights = lights self.queue = Queue() Thread.__init__(self) self.daemon = True self.start() def run(self): logger.debug("update playlist") self.position, self.playlist_items = update_playlist(self.playlist, position=self.get_position()) logger.debug("load playlist") self.player.loadlist(self.playlist) self.player.pause = True while self.running: logger.debug("play") self.play() self.position = (self.position + 1) % self.playlist_items logger.debug("ping") self.ping() logger.debug("wait") self.queue.get() def play(self): self.playing = True self.player.pause = False if self.lights: trigger_lights(self.player.path.decode()) self.player.wait_for_playback() self.player.pause = True self.playing = False def ping(self): data = 'NEXT' sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) logger.debug("ping: %s %s", self.peer, data) sock.sendto(bytes(data + "\n", "utf-8"), self.peer) received = str(sock.recv(1024), "utf-8") if received == 'WAITING' and not self.queue.qsize(): if self.first: self.queue.put('') self.first = False logger.debug("Sent: %s", data) logger.debug("Received: %s", received) def get_position(self): try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(1) data = 'POS' sock.sendto(bytes(data + "\n", "utf-8"), self.peer) received = str(sock.recv(1024), "utf-8") pos = int(received) except socket.timeout: logger.debug('no response', exc_info=1) pos = None return pos def my_log(loglevel, component, message): logger.debug('[{}] {}: {}'.format(loglevel, component, message)) def main(): parser = argparse.ArgumentParser(description='play 2 screens in sync') parser.add_argument('--peer', help='ip[:port] of peer', required=True) parser.add_argument('--port', type=int, help='local port', default=DEFAULT_PORT) parser.add_argument('--playlist', help='play.m3u', default='play.m3u') parser.add_argument('--window', action='store_true', help='run in window', default=False) parser.add_argument('--debug', action='store_true', help='debug', default=False) parser.add_argument('--lights', action='store_true', help='lights', default=False) args = parser.parse_args() if ':' in args.peer: peer = args.peer.split(':') peer = (peer[0], int(peer[1])) else: peer = (args.peer, DEFAULT_PORT) DEBUG = args.debug if DEBUG: log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s' logging.basicConfig(level=logging.DEBUG, format=log_format) base = os.path.dirname(os.path.abspath(__file__)) os.chdir(base) if args.lights: time.sleep(5) player = mpv.MPV( log_handler=my_log, input_default_bindings=True, input_vo_keyboard=True, sub_text_font_size=28, sub_text_font='Menlo' ) player.fullscreen = not args.window player.loop = 'inf' server = ThreadingUDPServer(('0.0.0.0', args.port), Handler) server.player = Player(peer, player, args.playlist, args.lights) player.register_key_binding('q', server.q_binding) logger.debug("listen on %s...", args.port) try: server.serve_forever() except KeyboardInterrupt: pass del player if __name__ == "__main__": main()