268 lines
9.1 KiB
Python
Executable file
268 lines
9.1 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
|
|
|
|
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
|
|
|
|
def stop(self, *args):
|
|
self.active = False
|
|
|
|
def time_pos_cb(self, pos, *args, **kwargs):
|
|
self._tick = time.time()
|
|
if self._pos != self.mpv.playlist_current_pos:
|
|
self._pos = self.mpv.playlist_current_pos
|
|
try:
|
|
track = self.mpv.playlist[self._pos]
|
|
logger.error("%s %s", datetime.now(), track["filename"])
|
|
except:
|
|
pass
|
|
|
|
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()
|