pandora_p_for_power/player/player.py

263 lines
8.9 KiB
Python
Executable file

#!/usr/bin/python3
import argparse
import collections
import json
import os
import time
from threading import Thread
from datetime import datetime
import ox
import mpv
import logging
logger = logging.getLogger('p_for_power')
SYNC_TOLERANCE = 0.05
SYNC_GRACE_TIME = 5
SYNC_JUMP_AHEAD = 1
PORT = 9067
DEBUG = False
CONFIG = {
"vf": None,
"sync_group": None,
}
def hide_gnome_overview():
import dbus
bus = dbus.SessionBus()
shell = bus.get_object('org.gnome.Shell', '/org/gnome/Shell')
props = dbus.Interface(shell, 'org.freedesktop.DBus.Properties')
props.Set('org.gnome.Shell', 'OverviewActive', False)
def mpv_log(loglevel, component, message):
logger.info('[{}] {}: {}'.format(loglevel, component, message))
class Main:
playlist_current_pos = -1
time_pos = -1
class Sync(Thread):
active = True
is_main = True
is_paused = False
ready = False
destination = "255.255.255.255"
reload_check = None
_pos = None
_tick = 0
need_to_sync = False
loops = []
def __init__(self, *args, **kwargs):
self.is_main = kwargs.get('mode', 'main') == 'main'
self.start_at_hour = kwargs.get("hour", False)
self.main = Main()
if kwargs.get("music"):
music = mpv.MPV(
log_handler=mpv_log,
input_default_bindings=False,
input_vo_keyboard=False,
)
music.loop_file = True
music.play("/srv/p_for_power/render/music-5.1.mp4")
self.loops.append(music)
if kwargs.get("forest"):
forest = mpv.MPV(
log_handler=mpv_log,
input_default_bindings=False,
input_vo_keyboard=False,
)
forest.loop_file = True
forest.play("/srv/p_for_power/render/forest-5.1.mp4")
self.loops.append(forest)
self.mpv = mpv.MPV(
log_handler=mpv_log,
input_default_bindings=True,
input_vo_keyboard=True,
)
if CONFIG.get("vf"):
self.mpv.vf = CONFIG["vf"]
self.mpv.observe_property('time-pos', self.time_pos_cb)
self.mpv.fullscreen = kwargs.get('fullscreen', False)
self.mpv.loop_file = False
self.mpv.loop_playlist = True
self.mpv.register_key_binding('q', self.q_binding)
self.mpv.register_key_binding('s', self.s_binding)
self.mpv.register_key_binding('p', self.p_binding)
self.mpv.register_key_binding('SPACE', self.space_binding)
self.playlist = kwargs['playlist']
self.playlist_mtime = os.stat(self.playlist).st_mtime
self.mpv.loadlist(self.playlist)
logger.error("loaded paylist: %s", self.playlist)
logger.debug("current playlist: %s", json.dumps(self.mpv.playlist, indent=2))
self.deviations = collections.deque(maxlen=10)
if self.start_at_hour:
self.mpv.pause = True
fmt = '%Y-%m-%d %H'
now = datetime.now()
offset = (now - datetime.strptime(now.strftime(fmt), fmt)).total_seconds()
for loop in self.loops:
loop.wait_until_playing()
loop.seek(offset, 'absolute', 'exact')
loop.pause = True
position = 0
for idx, item in enumerate(self.mpv.playlist):
duration = ox.avinfo(item['filename'])['duration']
if position + duration > offset:
pos = offset - position
self.mpv.playlist_play_index(idx)
self.mpv.pause = False
self.mpv.wait_until_playing()
self.mpv.seek(pos, 'absolute', 'exact')
time.sleep(0.1)
break
else:
position += duration
for loop in self.loops:
loop.pause = False
self.ready = True
Thread.__init__(self)
self.start()
def run(self):
while self.active:
time.sleep(0.5)
self.reload_playlist()
if not self.is_paused and self._tick and abs(time.time() - self._tick) > 60:
logger.error("player is stuck")
self._tick = 0
self.stop()
self.mpv.stop()
def is_keydown(self, args):
if args and args[0] and args[0][0] == 'd':
return True
return False
def q_binding(self, *args):
if self.is_keydown(args):
self.stop()
self.mpv.stop()
def space_binding(self, *args):
if self.is_keydown(args):
if self.mpv.pause:
self.p_binding(*args)
else:
self.s_binding(*args)
def s_binding(self, *args):
if self.is_keydown(args):
self.is_paused = True
self.mpv.pause = True
for loop in self.loops:
loop.pause = True
self.send_playback_state()
def p_binding(self, *args):
if self.is_keydown(args):
self.is_paused = False
self._tick = 0
self.mpv.pause = False
for loop in self.loops:
loop.pause = False
self.send_playback_state()
def stop(self, *args):
self.active = False
def time_pos_cb(self, pos, *args, **kwargs):
self._tick = time.time()
def reload_playlist(self):
if not self.reload_check:
self.reload_check = time.time()
if time.time() - self.reload_check > 5:
self.reload_check = time.time()
playlist_mtime = os.stat(self.playlist).st_mtime
if self.playlist_mtime != playlist_mtime:
self.playlist_mtime = playlist_mtime
#self.mpv.loadlist(self.playlist)
with open(self.playlist) as fd:
items = fd.read().strip().split('\n')
base = os.path.dirname(self.playlist)
items = [os.path.join(base, item) for item in items]
current_items = self.mpv.playlist_filenames
for filename in items:
if filename not in current_items:
self.mpv.playlist_append(filename)
logger.error("add: %s", filename)
remove = []
for filename in current_items:
if filename not in items:
remove.append(filename)
for filename in remove:
for idx, item in enumerate(self.mpv.playlist):
if item["filename"] == filename:
logger.error("remove: %s %s", idx, filename)
self.mpv.playlist_remove(idx)
break
for idx, filename in enumerate(items):
current_idx = self.mpv.playlist_filenames.index(filename)
if idx != current_idx:
logger.error("move item %s %s -> %s", filename, current_idx, idx)
self.mpv.playlist_move(current_idx, idx)
logger.error("reloaded paylist: %s", self.playlist)
logger.debug("current playlist: %s", json.dumps(self.mpv.playlist, indent=2))
def main():
parser = argparse.ArgumentParser(description='p_for_power player')
parser.add_argument('--mode', help='peer or main', default="main")
parser.add_argument('--playlist', default='/srv/p_for_power/render/front.m3u', help="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('--hour', action='store_true', help='hour', default=False)
parser.add_argument('--no-forest', action='store_false', help='disable background forest loop', default=False)
parser.add_argument('--no-music', action='store_false', help='disable background music loop', default=False)
parser.add_argument('--config', help='config', default=None)
args = parser.parse_args()
DEBUG = args.debug
if DEBUG:
log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
logging.basicConfig(level=logging.DEBUG, format=log_format)
if args.config:
if os.path.exists(args.config):
with open(args.config) as fd:
CONFIG.update(json.load(fd))
else:
logger.error("config file %s does not exist, skipping", args.config)
base = os.path.dirname(os.path.abspath(__file__))
#os.chdir(base)
player = Sync(
mode=args.mode, playlist=args.playlist, fullscreen=not args.window, hour=args.hour,
music=not args.no_music,
forest=not args.no_forest
)
while player.active:
try:
player.mpv.wait_for_playback()
except:
break
player.stop()
if __name__ == "__main__":
try:
hide_gnome_overview()
except:
pass
main()