From 6e4e8ca7a615f824255f49357d8a0320409dbc05 Mon Sep 17 00:00:00 2001 From: j Date: Mon, 22 Jan 2024 15:06:40 +0100 Subject: [PATCH] to infinity --- etc/systemd/system/render-infinity.service | 15 +++++ management/commands/infinity.py | 19 +++++++ player/player.py | 56 ++++++++++++++---- render.py | 66 ++++++++++++++++++++++ 4 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 etc/systemd/system/render-infinity.service create mode 100644 management/commands/infinity.py diff --git a/etc/systemd/system/render-infinity.service b/etc/systemd/system/render-infinity.service new file mode 100644 index 0000000..3ce81b2 --- /dev/null +++ b/etc/systemd/system/render-infinity.service @@ -0,0 +1,15 @@ +[Unit] +Description=render to infinity and beyond +After=pandora.service + +[Service] +Type=simple +Restart=always +User=pandora +Group=pandora +Nice=-15 +WorkingDirectory=/srv/pandora/pandora +ExecStart=/srv/pandora/pandora/manage.py infinity + +[Install] +WantedBy=multi-user.target diff --git a/management/commands/infinity.py b/management/commands/infinity.py new file mode 100644 index 0000000..1dc85e3 --- /dev/null +++ b/management/commands/infinity.py @@ -0,0 +1,19 @@ +import json +import os +import subprocess + +from django.core.management.base import BaseCommand +from django.conf import settings + +from ...render import render_infinity + + +class Command(BaseCommand): + help = 'render infinity' + + def add_arguments(self, parser): + parser.add_argument('--prefix', action='store', dest='prefix', default="/srv/t_for_time", help='prefix to build clips in') + parser.add_argument('--duration', action='store', dest='duration', default="3600", help='target duration of all fragments in seconds') + + def handle(self, **options): + render_infinity(options) diff --git a/player/player.py b/player/player.py index 8402697..74800e9 100755 --- a/player/player.py +++ b/player/player.py @@ -1,8 +1,9 @@ #!/usr/bin/python3 import argparse +import collections +import json import os import socket -import collections import time from threading import Thread from datetime import datetime @@ -57,13 +58,22 @@ class Sync(Thread): if self.is_main: self.socket_enable_broadcast() - self.mpv = mpv.MPV( - log_handler=mpv_log, input_default_bindings=True, - input_vo_keyboard=True, - sub_text_font_size=FONT_SIZE, sub_text_font=FONT, - sub_border_size=FONT_BORDER, - sub_margin_y=SUB_MARGIN, - ) + if mpv.MPV_VERSION >= (2, 2): + self.mpv = mpv.MPV( + log_handler=mpv_log, input_default_bindings=True, + input_vo_keyboard=True, + sub_font_size=FONT_SIZE, sub_font=FONT, + sub_border_size=FONT_BORDER, + sub_margin_y=SUB_MARGIN, + ) + else: + self.mpv = mpv.MPV( + log_handler=mpv_log, input_default_bindings=True, + input_vo_keyboard=True, + sub_text_font_size=FONT_SIZE, sub_text_font=FONT, + sub_border_size=FONT_BORDER, + sub_margin_y=SUB_MARGIN, + ) self.mpv.observe_property('time-pos', self.time_pos_cb) self.mpv.fullscreen = kwargs.get('fullscreen', False) self.mpv.loop_file = False @@ -73,6 +83,7 @@ class Sync(Thread): 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 not self.is_main: self.mpv.pause = False @@ -135,8 +146,33 @@ class Sync(Thread): playlist_mtime = os.stat(self.playlist).st_mtime if self.playlist_mtime != playlist_mtime: self.playlist_mtime = playlist_mtime - self.mpv.loadlist(self.playlist) + #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 init_socket(self): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) @@ -261,7 +297,7 @@ def main(): prefix = os.path.expanduser('~/Videos/t_for_time') parser = argparse.ArgumentParser(description='t_for_time sync player') - parser.add_argument('--mode', help='ip of peer', default="peer") + parser.add_argument('--mode', help='peer or main', default="peer") parser.add_argument('--playlist', default='/srv/t_for_time/render/128/front.m3u', help="m3u") parser.add_argument('--prefix', help='video location', default=prefix) parser.add_argument('--window', action='store_true', help='run in window', default=False) diff --git a/render.py b/render.py index cf3b45b..77e3824 100644 --- a/render.py +++ b/render.py @@ -1,5 +1,6 @@ #!/usr/bin/python3 from collections import defaultdict +from glob import glob import json import os import re @@ -609,3 +610,68 @@ def update_subtitles(options): srt = ox.srt.encode(subs) write_if_new(str(path), srt, 'b') + +def update_m3u(render_prefix, exclude=[]): + files = ox.sorted_strings(glob(render_prefix + "*/*/back.mp4")) + for ex in exclude: + files = [f for f in files if not f.startswith(ex + "/")] + back_m3u = "\n".join(files) + back_m3u = back_m3u.replace(render_prefix, "") + front_m3u = back_m3u.replace("back.mp4", "front.mp4") + + back_m3u_f = render_prefix + "back.m3u" + front_m3u_f = render_prefix + "front.m3u" + + with open(back_m3u_f + "_", "w") as fd: + fd.write(back_m3u) + with open(front_m3u_f + "_", "w") as fd: + fd.write(front_m3u) + shutil.move(front_m3u_f + "_", front_m3u_f) + cmd = ["scp", front_m3u_f, "front:" + front_m3u_f] + subprocess.check_call(cmd) + shutil.move(back_m3u_f + "_", back_m3u_f) + + +def render_infinity(options): + prefix = options['prefix'] + duration = int(options['duration']) + base = int(options['offset']) + + state_f = os.path.join(prefix, "infinity.json") + if os.path.exists(state_f): + with open(state_f) as fd: + state = json.load(fd) + else: + state = { + "offset": 100, + "max-items": 30 + } + for key in ("prefix", "duration"): + state[key] = options[key] + + while True: + render_prefix = state["prefix"] + "/render/" + current = [ + f for f in os.listdir(render_prefix) + if f.isdigit() and os.path.isdir(render_prefix + f) + ] + if len(current) > state["max-items"]: + current = ox.sorted_strings(current) + remove = current[-state["max-items"]:] + update_m3u(render_prefix, exclude=remove) + for folder in remove: + folder = render_prefix + folder + print("remove", folder) + #shutil.rmtree(folder) + cmd = ["ssh", "front", "rm", "-r", folder] + print(cmd) + #subprocess.check_call(cmd) + render_all(state) + path = "%s%s/" % (render_prefix, state["offset"]) + cmd = ['rsync', '-a', path, "front:" + path] + subprocess.check_call(cmd) + update_m3u(render_prefix) + state["offset"] += 1 + with open(state_f + "~", "w") as fd: + json.dump(state, fd, indent=2) + shutil.move(state_f + "~", state_f)