Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
c8991438bb | |||
45e2acbbb8 | |||
e221626191 | |||
9021131e8d | |||
44bd62897c | |||
793da444ad | |||
4f57230996 | |||
1ac5574bfc | |||
3c9f200fdd | |||
569c72ee8b | |||
7654fc7d6c | |||
f8bb75cd5b | |||
8268166b77 | |||
6cdbf4f1b9 | |||
a6479d1746 | |||
19b54d57cb | |||
d72bf343e3 | |||
ed03c7026a | |||
438108a8f9 | |||
3782ca6721 | |||
80db2f0255 | |||
01f669b61d |
9 changed files with 345 additions and 47 deletions
|
@ -2,24 +2,43 @@ import json
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
import ox
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from ...render import add_translations
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'export all subtitles for translations'
|
help = 'export all subtitles for translations'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
pass
|
parser.add_argument('--lang', action='store', dest='lang', default=None, help='subtitle language')
|
||||||
|
|
||||||
def handle(self, **options):
|
def handle(self, **options):
|
||||||
|
|
||||||
import annotation.models
|
import annotation.models
|
||||||
import item.models
|
import item.models
|
||||||
|
lang = options["lang"]
|
||||||
|
if lang:
|
||||||
|
lang = lang.split(',')
|
||||||
|
tlang = lang[1:]
|
||||||
|
lang = lang[0]
|
||||||
|
else:
|
||||||
|
tlang = None
|
||||||
|
if lang == "en":
|
||||||
|
lang = None
|
||||||
|
|
||||||
for i in item.models.Item.objects.filter(data__type__contains='Voice Over').order_by('sort__title'):
|
for i in item.models.Item.objects.filter(data__type__contains='Voice Over').order_by('sort__title'):
|
||||||
print("## %s %s" % (i.get("title"), i.public_id))
|
print("## %s %s" % (i.get("title"), i.public_id))
|
||||||
for sub in i.annotations.all().filter(layer='subtitles').exclude(value='').order_by("start"):
|
for sub in i.annotations.all().filter(layer='subtitles').exclude(value='').filter(languages=lang).order_by("start"):
|
||||||
if not sub.languages:
|
if tlang:
|
||||||
print(sub.value.strip() + "\n")
|
value = add_translations(sub, tlang)
|
||||||
|
value = ox.strip_tags(value)
|
||||||
|
else:
|
||||||
|
value = sub.value.replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
||||||
|
if sub.languages:
|
||||||
|
value = ox.strip_tags(value)
|
||||||
|
print(value.strip() + "\n")
|
||||||
print("\n\n\n")
|
print("\n\n\n")
|
||||||
|
|
||||||
|
|
|
@ -28,10 +28,20 @@ class Command(BaseCommand):
|
||||||
help = 'generate symlinks to clips and clips.json'
|
help = 'generate symlinks to clips and clips.json'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--lang', action='store', dest='lang', default=None, help='subtitle language')
|
||||||
parser.add_argument('--prefix', action='store', dest='prefix', default="/srv/t_for_time", help='prefix to build clips in')
|
parser.add_argument('--prefix', action='store', dest='prefix', default="/srv/t_for_time", help='prefix to build clips in')
|
||||||
|
|
||||||
def handle(self, **options):
|
def handle(self, **options):
|
||||||
prefix = options['prefix']
|
prefix = options['prefix']
|
||||||
|
lang = options["lang"]
|
||||||
|
if lang:
|
||||||
|
lang = lang.split(',')
|
||||||
|
tlang = lang[1:]
|
||||||
|
lang = lang[0]
|
||||||
|
else:
|
||||||
|
tlang = None
|
||||||
|
if lang == "en":
|
||||||
|
lang = None
|
||||||
clips = []
|
clips = []
|
||||||
for i in item.models.Item.objects.filter(sort__type='original'):
|
for i in item.models.Item.objects.filter(sort__type='original'):
|
||||||
qs = item.models.Item.objects.filter(data__title=i.data['title']).exclude(id=i.id)
|
qs = item.models.Item.objects.filter(data__title=i.data['title']).exclude(id=i.id)
|
||||||
|
@ -102,8 +112,8 @@ class Command(BaseCommand):
|
||||||
os.unlink(target)
|
os.unlink(target)
|
||||||
os.symlink(src, target)
|
os.symlink(src, target)
|
||||||
subs = []
|
subs = []
|
||||||
for sub in vo.annotations.filter(layer="subtitles").exclude(value="").order_by("start"):
|
for sub in vo.annotations.filter(layer="subtitles", languages=lang).exclude(value="").order_by("start"):
|
||||||
sdata = get_srt(sub)
|
sdata = get_srt(sub, lang=tlang)
|
||||||
subs.append(sdata)
|
subs.append(sdata)
|
||||||
voice_over[fragment_id][batch] = {
|
voice_over[fragment_id][batch] = {
|
||||||
"src": target,
|
"src": target,
|
||||||
|
|
109
management/commands/import_subtitles.py
Normal file
109
management/commands/import_subtitles.py
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import ox
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
from item.models import Item
|
||||||
|
from annotation.models import Annotation
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'export all subtitles for translations'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--lang', action='store', dest='lang', default=None, help='subtitle language')
|
||||||
|
parser.add_argument('--test', action='store_true', dest='test', default=False, help='test run')
|
||||||
|
parser.add_argument('args', metavar='args', type=str, nargs='*', help='file or url')
|
||||||
|
|
||||||
|
def handle(self, filename, **options):
|
||||||
|
if not options["lang"]:
|
||||||
|
print("--lang is required")
|
||||||
|
return
|
||||||
|
lang = options["lang"]
|
||||||
|
|
||||||
|
if filename.startswith("http"):
|
||||||
|
data = ox.net.read_url(filename).decode()
|
||||||
|
else:
|
||||||
|
with open(filename) as fd:
|
||||||
|
data = fd.read()
|
||||||
|
|
||||||
|
data = ('\n' + data.strip()).split('\n## ')[1:]
|
||||||
|
|
||||||
|
invalid = []
|
||||||
|
valid = []
|
||||||
|
for block in data:
|
||||||
|
title, block = block.split('\n', 1)
|
||||||
|
block = block.strip()
|
||||||
|
title = title.strip()
|
||||||
|
item_id = title.split(' ')[-1]
|
||||||
|
item = Item.objects.get(public_id=item_id)
|
||||||
|
|
||||||
|
subtitles_en = item.annotations.filter(layer="subtitles", languages=None).exclude(value='')
|
||||||
|
lines = block.split('\n\n')
|
||||||
|
if len(lines) != subtitles_en.count():
|
||||||
|
print('%s: number of subtitles does not match, en: %s vs %s: %s' % (title, subtitles_en.count(), lang, len(lines)))
|
||||||
|
if options["test"]:
|
||||||
|
print(json.dumps(lines, indent=2, ensure_ascii=False))
|
||||||
|
print(json.dumps([s.value for s in subtitles_en.order_by('start')], indent=2, ensure_ascii=False))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if options["test"]:
|
||||||
|
print('%s: valid %s subtitles' % (title, len(lines)))
|
||||||
|
else:
|
||||||
|
n = 0
|
||||||
|
item.annotations.filter(layer="subtitles", languages=lang).delete()
|
||||||
|
for sub_en in subtitles_en.order_by('start'):
|
||||||
|
sub = Annotation()
|
||||||
|
sub.item = sub_en.item
|
||||||
|
sub.user = sub_en.user
|
||||||
|
sub.layer = sub_en.layer
|
||||||
|
sub.start = sub_en.start
|
||||||
|
sub.end = sub_en.end
|
||||||
|
sub.value = '<span lang="%s">%s</span>' % (lang, lines[n])
|
||||||
|
sub.save()
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
'''
|
||||||
|
srt = 'vocals_txt/%s/%s' % (title[0], title.replace('.wav', '.srt'))
|
||||||
|
filename = 'vocals_txt/%s/%s' % (title[0], title.replace('.wav', '.' + lang + '.srt'))
|
||||||
|
|
||||||
|
folder = os.path.dirname(filename)
|
||||||
|
if not os.path.exists(folder):
|
||||||
|
os.makedirs(folder)
|
||||||
|
data = json.load(open(srt + '.json'))
|
||||||
|
subs = block.replace('\n\n', '\n').split('\n')
|
||||||
|
if len(data) != len(subs):
|
||||||
|
print('invalid', title, 'expected', len(data), 'got', len(subs))
|
||||||
|
invalid.append('## %s\n\n%s' % (title, block))
|
||||||
|
valid.append('## %s\n\n%s' % (title, '\n\n'.join([d['value'] for d in data])))
|
||||||
|
continue
|
||||||
|
|
||||||
|
for i, sub in enumerate(data):
|
||||||
|
sub['value'] = subs[i]
|
||||||
|
kodata = ox.srt.encode(data)
|
||||||
|
current = None
|
||||||
|
if os.path.exists(filename):
|
||||||
|
with open(filename, 'rb') as fd:
|
||||||
|
current = fd.read()
|
||||||
|
if current != kodata:
|
||||||
|
print('update', title, filename)
|
||||||
|
with open(filename, 'wb') as fd:
|
||||||
|
fd.write(kodata)
|
||||||
|
with open(filename + '.json', 'w') as fd:
|
||||||
|
ko = [{
|
||||||
|
'in': s['in'],
|
||||||
|
'out': s['out'],
|
||||||
|
'value': s['value'],
|
||||||
|
} for s in data]
|
||||||
|
json.dump(ko, fd, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
if invalid:
|
||||||
|
with open('invalid_%s_subtitles.txt' % lang, 'w') as fd:
|
||||||
|
fd.write('\n\n\n\n'.join(invalid))
|
||||||
|
with open('invalid_%s_subtitles_en.txt' % lang, 'w') as fd:
|
||||||
|
fd.write('\n\n\n\n'.join(valid))
|
||||||
|
'''
|
|
@ -13,8 +13,8 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
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('--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')
|
|
||||||
parser.add_argument('--offset', action='store', dest='offset', default="1024", help='inital offset in pi')
|
parser.add_argument('--offset', action='store', dest='offset', default="1024", help='inital offset in pi')
|
||||||
|
parser.add_argument('--lang', action='store', dest='lang', default=None, help='subtitle language')
|
||||||
|
|
||||||
def handle(self, **options):
|
def handle(self, **options):
|
||||||
update_subtitles(options)
|
update_subtitles(options)
|
||||||
|
|
145
player/player.py
145
player/player.py
|
@ -8,6 +8,7 @@ import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import ox
|
||||||
import mpv
|
import mpv
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,10 +20,17 @@ SYNC_GRACE_TIME = 5
|
||||||
SYNC_JUMP_AHEAD = 1
|
SYNC_JUMP_AHEAD = 1
|
||||||
PORT = 9067
|
PORT = 9067
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
FONT = 'Menlo'
|
|
||||||
FONT_SIZE = 30
|
CONFIG = {
|
||||||
FONT_BORDER = 4
|
"font": "Menlo",
|
||||||
SUB_MARGIN = 2 * 36 + 6
|
"font_size": 30,
|
||||||
|
"font_border": 4,
|
||||||
|
"sub_border_color": "0.0/0.0/0.0/0.75",
|
||||||
|
"sub_margin": 2 * 36 + 6,
|
||||||
|
"sub_spacing": 0,
|
||||||
|
"vf": None,
|
||||||
|
"sync_group": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def hide_gnome_overview():
|
def hide_gnome_overview():
|
||||||
|
@ -44,6 +52,7 @@ class Main:
|
||||||
class Sync(Thread):
|
class Sync(Thread):
|
||||||
active = True
|
active = True
|
||||||
is_main = True
|
is_main = True
|
||||||
|
is_paused = False
|
||||||
ready = False
|
ready = False
|
||||||
destination = "255.255.255.255"
|
destination = "255.255.255.255"
|
||||||
reload_check = None
|
reload_check = None
|
||||||
|
@ -53,32 +62,52 @@ class Sync(Thread):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.is_main = kwargs.get('mode', 'main') == 'main'
|
self.is_main = kwargs.get('mode', 'main') == 'main'
|
||||||
|
self.start_at_hour = kwargs.get("hour", False)
|
||||||
self.sock = self.init_socket()
|
self.sock = self.init_socket()
|
||||||
self.main = Main()
|
self.main = Main()
|
||||||
if self.is_main:
|
if self.is_main:
|
||||||
self.socket_enable_broadcast()
|
self.socket_enable_broadcast()
|
||||||
|
|
||||||
|
if kwargs.get("sax"):
|
||||||
|
self.sax = mpv.MPV(
|
||||||
|
log_handler=mpv_log, input_default_bindings=True,
|
||||||
|
input_vo_keyboard=True,
|
||||||
|
)
|
||||||
|
self.sax.loop_file = True
|
||||||
|
self.sax.play("/srv/t_for_time/render/Saxophone-5.1.mp4")
|
||||||
|
else:
|
||||||
|
self.sax = None
|
||||||
|
|
||||||
if mpv.MPV_VERSION >= (2, 2):
|
if mpv.MPV_VERSION >= (2, 2):
|
||||||
self.mpv = mpv.MPV(
|
self.mpv = mpv.MPV(
|
||||||
log_handler=mpv_log, input_default_bindings=True,
|
log_handler=mpv_log, input_default_bindings=True,
|
||||||
input_vo_keyboard=True,
|
input_vo_keyboard=True,
|
||||||
sub_font_size=FONT_SIZE, sub_font=FONT,
|
sub_font_size=CONFIG["font_size"], sub_font=CONFIG["font"],
|
||||||
sub_border_size=FONT_BORDER,
|
sub_border_size=CONFIG["font_border"],
|
||||||
sub_margin_y=SUB_MARGIN,
|
sub_border_color=CONFIG["sub_border_color"],
|
||||||
|
sub_margin_y=CONFIG["sub_margin"],
|
||||||
|
sub_ass_line_spacing=CONFIG["sub_spacing"],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.mpv = mpv.MPV(
|
self.mpv = mpv.MPV(
|
||||||
log_handler=mpv_log, input_default_bindings=True,
|
log_handler=mpv_log, input_default_bindings=True,
|
||||||
input_vo_keyboard=True,
|
input_vo_keyboard=True,
|
||||||
sub_text_font_size=FONT_SIZE, sub_text_font=FONT,
|
sub_text_font_size=CONFIG["font_size"], sub_text_font=CONFIG["font"],
|
||||||
sub_border_size=FONT_BORDER,
|
sub_border_size=CONFIG["font_border"],
|
||||||
sub_margin_y=SUB_MARGIN,
|
sub_border_color=CONFIG["sub_border_color"],
|
||||||
|
sub_margin_y=CONFIG["sub_margin"],
|
||||||
|
sub_ass_line_spacing=CONFIG["sub_spacing"],
|
||||||
)
|
)
|
||||||
|
if CONFIG.get("vf"):
|
||||||
|
self.mpv.vf = CONFIG["vf"]
|
||||||
self.mpv.observe_property('time-pos', self.time_pos_cb)
|
self.mpv.observe_property('time-pos', self.time_pos_cb)
|
||||||
self.mpv.fullscreen = kwargs.get('fullscreen', False)
|
self.mpv.fullscreen = kwargs.get('fullscreen', False)
|
||||||
self.mpv.loop_file = False
|
self.mpv.loop_file = False
|
||||||
self.mpv.loop_playlist = True
|
self.mpv.loop_playlist = True
|
||||||
self.mpv.register_key_binding('q', self.q_binding)
|
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 = kwargs['playlist']
|
||||||
self.playlist_mtime = os.stat(self.playlist).st_mtime
|
self.playlist_mtime = os.stat(self.playlist).st_mtime
|
||||||
self.mpv.loadlist(self.playlist)
|
self.mpv.loadlist(self.playlist)
|
||||||
|
@ -90,6 +119,31 @@ class Sync(Thread):
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
self.mpv.pause = True
|
self.mpv.pause = True
|
||||||
self.sync_to_main()
|
self.sync_to_main()
|
||||||
|
elif 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()
|
||||||
|
if self.sax:
|
||||||
|
self.sax.wait_until_playing()
|
||||||
|
self.sax.seek(offset, 'absolute', 'exact')
|
||||||
|
self.sax.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
|
||||||
|
if self.sax:
|
||||||
|
self.sax.pause = False
|
||||||
|
|
||||||
self.ready = True
|
self.ready = True
|
||||||
Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.start()
|
self.start()
|
||||||
|
@ -106,16 +160,45 @@ class Sync(Thread):
|
||||||
else:
|
else:
|
||||||
self.read_position_main()
|
self.read_position_main()
|
||||||
self.reload_playlist()
|
self.reload_playlist()
|
||||||
if self._tick and abs(time.time() - self._tick) > 60:
|
if not self.is_paused and self._tick and abs(time.time() - self._tick) > 60:
|
||||||
logger.error("player is stuck")
|
logger.error("player is stuck")
|
||||||
self._tick = 0
|
self._tick = 0
|
||||||
self.stop()
|
self.stop()
|
||||||
self.mpv.stop()
|
self.mpv.stop()
|
||||||
|
|
||||||
def q_binding(self, *args):
|
def q_binding(self, *args):
|
||||||
|
if args[0] != 'd-':
|
||||||
|
return
|
||||||
self.stop()
|
self.stop()
|
||||||
self.mpv.stop()
|
self.mpv.stop()
|
||||||
|
|
||||||
|
def space_binding(self, *args):
|
||||||
|
if args[0] != 'd-':
|
||||||
|
return
|
||||||
|
if self.mpv.pause:
|
||||||
|
self.p_binding(*args)
|
||||||
|
else:
|
||||||
|
self.s_binding(*args)
|
||||||
|
|
||||||
|
def s_binding(self, *args):
|
||||||
|
if args[0] != 'd-':
|
||||||
|
return
|
||||||
|
self.is_paused = True
|
||||||
|
self.mpv.pause = True
|
||||||
|
if self.sax:
|
||||||
|
self.sax.pause = True
|
||||||
|
self.send_playback_state()
|
||||||
|
|
||||||
|
def p_binding(self, *args):
|
||||||
|
if args[0] != 'd-':
|
||||||
|
return
|
||||||
|
self.is_paused = False
|
||||||
|
self._tick = 0
|
||||||
|
self.mpv.pause = False
|
||||||
|
if self.sax:
|
||||||
|
self.sax.pause = False
|
||||||
|
self.send_playback_state()
|
||||||
|
|
||||||
def stop(self, *args):
|
def stop(self, *args):
|
||||||
self.active = False
|
self.active = False
|
||||||
if self.sock:
|
if self.sock:
|
||||||
|
@ -195,6 +278,8 @@ class Sync(Thread):
|
||||||
"%0.4f %s"
|
"%0.4f %s"
|
||||||
% (self.mpv.time_pos, self.mpv.playlist_current_pos)
|
% (self.mpv.time_pos, self.mpv.playlist_current_pos)
|
||||||
).encode()
|
).encode()
|
||||||
|
if CONFIG.get("sync_group"):
|
||||||
|
msg = ("%s " % CONFIG["sync_group"]).encode() + msg
|
||||||
except:
|
except:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -202,18 +287,47 @@ class Sync(Thread):
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
logger.error("send failed: %s", e)
|
logger.error("send failed: %s", e)
|
||||||
|
|
||||||
|
def send_playback_state(self):
|
||||||
|
state = 'pause' if self.mpv.pause else 'play'
|
||||||
|
msg = ("%s -1" % state).encode()
|
||||||
|
try:
|
||||||
|
self.sock.send(msg)
|
||||||
|
except socket.error as e:
|
||||||
|
logger.error("send failed: %s", e)
|
||||||
|
|
||||||
#
|
#
|
||||||
# follower specific
|
# follower specific
|
||||||
#
|
#
|
||||||
|
_last_ping = None
|
||||||
|
|
||||||
def read_position_main(self):
|
def read_position_main(self):
|
||||||
self.sock.settimeout(5)
|
self.sock.settimeout(5)
|
||||||
|
while True:
|
||||||
try:
|
try:
|
||||||
data = self.sock.recvfrom(1024)[0].decode().split(" ", 1)
|
data = self.sock.recvfrom(1024)[0].decode().split(" ", 1)
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
|
if self._last_ping != "pause":
|
||||||
logger.error("failed to receive data from main")
|
logger.error("failed to receive data from main")
|
||||||
|
return
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.error("socket closed")
|
logger.error("socket closed")
|
||||||
|
return
|
||||||
|
|
||||||
|
if CONFIG.get("sync_group"):
|
||||||
|
if data[0] == str(CONFIG["sync_group"]):
|
||||||
|
data = data[1].split(" ", 1)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
self._last_ping = data[0]
|
||||||
|
if data[0] == "pause":
|
||||||
|
self.is_paused = True
|
||||||
|
self.mpv.pause = True
|
||||||
|
elif data[0] == "play":
|
||||||
|
self.is_paused = False
|
||||||
|
self._tick = 0
|
||||||
|
self.mpv.pause = False
|
||||||
else:
|
else:
|
||||||
self.main.time_pos = float(data[0])
|
self.main.time_pos = float(data[0])
|
||||||
self.main.playlist_current_pos = int(data[1])
|
self.main.playlist_current_pos = int(data[1])
|
||||||
|
@ -302,16 +416,23 @@ def main():
|
||||||
parser.add_argument('--prefix', help='video location', default=prefix)
|
parser.add_argument('--prefix', help='video location', default=prefix)
|
||||||
parser.add_argument('--window', action='store_true', help='run in window', default=False)
|
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('--debug', action='store_true', help='debug', default=False)
|
||||||
|
parser.add_argument('--hour', action='store_true', help='hour', default=False)
|
||||||
|
parser.add_argument('--sax', action='store_true', help='hour', default=False)
|
||||||
|
parser.add_argument('--config', help='config', default=None)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
DEBUG = args.debug
|
DEBUG = args.debug
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
|
log_format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s'
|
||||||
logging.basicConfig(level=logging.DEBUG, format=log_format)
|
logging.basicConfig(level=logging.DEBUG, format=log_format)
|
||||||
|
if args.config:
|
||||||
|
with open(args.config) as fd:
|
||||||
|
CONFIG.update(json.load(fd))
|
||||||
|
|
||||||
base = os.path.dirname(os.path.abspath(__file__))
|
base = os.path.dirname(os.path.abspath(__file__))
|
||||||
#os.chdir(base)
|
#os.chdir(base)
|
||||||
|
|
||||||
player = Sync(mode=args.mode, playlist=args.playlist, fullscreen=not args.window)
|
player = Sync(mode=args.mode, playlist=args.playlist, fullscreen=not args.window, hour=args.hour, sax=args.sax)
|
||||||
while player.active:
|
while player.active:
|
||||||
try:
|
try:
|
||||||
player.mpv.wait_for_playback()
|
player.mpv.wait_for_playback()
|
||||||
|
|
74
render.py
74
render.py
|
@ -132,17 +132,17 @@ def compose(clips, target=150, base=1024, voice_over=None):
|
||||||
subs = []
|
subs = []
|
||||||
for vo in voice_overs:
|
for vo in voice_overs:
|
||||||
voc = vo.copy()
|
voc = vo.copy()
|
||||||
a, b = '3', '-6'
|
a, b = '-11', '-3'
|
||||||
if 'Whispered' in voc['src']:
|
if 'Whispered' in voc['src']:
|
||||||
a, b = '6', '-3'
|
a, b = '-8', '0'
|
||||||
elif 'Read' in voc['src']:
|
elif 'Read' in voc['src']:
|
||||||
a, b = '6.25', '-2.75'
|
a, b = '-7.75', '0.25'
|
||||||
elif 'Free' in voc['src']:
|
elif 'Free' in voc['src']:
|
||||||
a, b = '5.2', '-3.8'
|
a, b = '-8.8', '-0.8'
|
||||||
elif 'Ashley' in voc['src']:
|
elif 'Ashley' in voc['src']:
|
||||||
a, b = '3.75', '-5.25'
|
a, b = '-9.5', '-1.50'
|
||||||
elif 'Melody' in voc['src']:
|
elif 'Melody' in voc['src']:
|
||||||
a, b = '4.25', '-4.75'
|
a, b = '-5.25', '-0.25'
|
||||||
voc['filter'] = {'volume': a}
|
voc['filter'] = {'volume': a}
|
||||||
scene['audio-center']['A1'].append(voc)
|
scene['audio-center']['A1'].append(voc)
|
||||||
vo_low = vo.copy()
|
vo_low = vo.copy()
|
||||||
|
@ -282,10 +282,10 @@ def compose(clips, target=150, base=1024, voice_over=None):
|
||||||
scene['audio-back']['A1'].append({
|
scene['audio-back']['A1'].append({
|
||||||
'duration': clip['duration'],
|
'duration': clip['duration'],
|
||||||
'src': clip['original'],
|
'src': clip['original'],
|
||||||
'filter': {'volume': '+0.2'},
|
'filter': {'volume': '-8.2'},
|
||||||
})
|
})
|
||||||
# TBD: Foley
|
# TBD: Foley
|
||||||
cf_volume = '-5.5'
|
cf_volume = '-2.5'
|
||||||
scene['audio-front']['A2'].append({
|
scene['audio-front']['A2'].append({
|
||||||
'duration': clip['duration'],
|
'duration': clip['duration'],
|
||||||
'src': foley,
|
'src': foley,
|
||||||
|
@ -308,6 +308,16 @@ def get_scene_duration(scene):
|
||||||
duration += clip['duration']
|
duration += clip['duration']
|
||||||
return duration
|
return duration
|
||||||
|
|
||||||
|
def get_offset_duration(prefix):
|
||||||
|
duration = 0
|
||||||
|
for root, folders, files in os.walk(prefix):
|
||||||
|
for f in files:
|
||||||
|
if f == 'scene.json':
|
||||||
|
path = os.path.join(root, f)
|
||||||
|
scene = json.load(open(path))
|
||||||
|
duration += get_scene_duration(scene)
|
||||||
|
return duration
|
||||||
|
|
||||||
def render(root, scene, prefix=''):
|
def render(root, scene, prefix=''):
|
||||||
fps = 24
|
fps = 24
|
||||||
files = []
|
files = []
|
||||||
|
@ -343,6 +353,7 @@ def render(root, scene, prefix=''):
|
||||||
files.append(path)
|
files.append(path)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
|
|
||||||
def get_fragments(clips, voice_over, prefix):
|
def get_fragments(clips, voice_over, prefix):
|
||||||
import itemlist.models
|
import itemlist.models
|
||||||
import item.models
|
import item.models
|
||||||
|
@ -541,7 +552,7 @@ def render_all(options):
|
||||||
shutil.move(fragment_prefix / "front-5.1.mp4", fragment_prefix / "front.mp4")
|
shutil.move(fragment_prefix / "front-5.1.mp4", fragment_prefix / "front.mp4")
|
||||||
for fn in (
|
for fn in (
|
||||||
"audio-5.1.mp4",
|
"audio-5.1.mp4",
|
||||||
"audio-center.wav", "audio-rear.wav", "audio-center.wav",
|
"audio-center.wav", "audio-rear.wav",
|
||||||
"audio-front.wav", "audio-back.wav", "back-audio.mp4",
|
"audio-front.wav", "audio-back.wav", "back-audio.mp4",
|
||||||
"fl.wav", "fr.wav", "fc.wav", "lfe.wav", "bl.wav", "br.wav",
|
"fl.wav", "fr.wav", "fc.wav", "lfe.wav", "bl.wav", "br.wav",
|
||||||
):
|
):
|
||||||
|
@ -555,9 +566,26 @@ def render_all(options):
|
||||||
json.dump(_CACHE, fd)
|
json.dump(_CACHE, fd)
|
||||||
|
|
||||||
|
|
||||||
def get_srt(sub, offset=0):
|
def add_translations(sub, lang):
|
||||||
|
value = sub.value.replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
||||||
|
if sub.languages:
|
||||||
|
value = ox.strip_tags(value)
|
||||||
|
if lang:
|
||||||
|
for slang in lang:
|
||||||
|
if slang == "en":
|
||||||
|
slang = None
|
||||||
|
for tsub in sub.item.annotations.filter(layer="subtitles", start=sub.start, end=sub.end, languages=slang):
|
||||||
|
tvalue = tsub.value.replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
||||||
|
if tsub.languages:
|
||||||
|
tvalue = ox.strip_tags(tvalue)
|
||||||
|
value += '\n' + tvalue
|
||||||
|
return value
|
||||||
|
|
||||||
|
def get_srt(sub, offset=0, lang=None):
|
||||||
sdata = sub.json(keys=['in', 'out', 'value'])
|
sdata = sub.json(keys=['in', 'out', 'value'])
|
||||||
sdata['value'] = sdata['value'].replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n')
|
sdata['value'] = sdata['value'].replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
||||||
|
if lang:
|
||||||
|
sdata['value'] = add_translations(sub, lang)
|
||||||
if offset:
|
if offset:
|
||||||
sdata["in"] += offset
|
sdata["in"] += offset
|
||||||
sdata["out"] += offset
|
sdata["out"] += offset
|
||||||
|
@ -578,8 +606,17 @@ def update_subtitles(options):
|
||||||
import item.models
|
import item.models
|
||||||
|
|
||||||
prefix = Path(options['prefix'])
|
prefix = Path(options['prefix'])
|
||||||
duration = int(options['duration'])
|
|
||||||
base = int(options['offset'])
|
base = int(options['offset'])
|
||||||
|
lang = options["lang"]
|
||||||
|
if lang and "," in lang:
|
||||||
|
lang = lang.split(',')
|
||||||
|
if isinstance(lang, list):
|
||||||
|
tlang = lang[1:]
|
||||||
|
lang = lang[0]
|
||||||
|
else:
|
||||||
|
tlang = None
|
||||||
|
if lang == "en":
|
||||||
|
lang = None
|
||||||
|
|
||||||
_cache = os.path.join(prefix, "cache.json")
|
_cache = os.path.join(prefix, "cache.json")
|
||||||
if os.path.exists(_cache):
|
if os.path.exists(_cache):
|
||||||
|
@ -589,7 +626,10 @@ def update_subtitles(options):
|
||||||
base_prefix = prefix / 'render' / str(base)
|
base_prefix = prefix / 'render' / str(base)
|
||||||
for folder in os.listdir(base_prefix):
|
for folder in os.listdir(base_prefix):
|
||||||
folder = base_prefix / folder
|
folder = base_prefix / folder
|
||||||
with open(folder / "scene.json") as fd:
|
scene_json = folder / "scene.json"
|
||||||
|
if not os.path.exists(scene_json):
|
||||||
|
continue
|
||||||
|
with open(scene_json) as fd:
|
||||||
scene = json.load(fd)
|
scene = json.load(fd)
|
||||||
offset = 0
|
offset = 0
|
||||||
subs = []
|
subs = []
|
||||||
|
@ -599,8 +639,8 @@ def update_subtitles(options):
|
||||||
vo = item.models.Item.objects.filter(data__batch__icontains=batch, data__title__startswith=fragment_id + '_').first()
|
vo = item.models.Item.objects.filter(data__batch__icontains=batch, data__title__startswith=fragment_id + '_').first()
|
||||||
if vo:
|
if vo:
|
||||||
#print("%s => %s %s" % (clip['src'], vo, vo.get('batch')))
|
#print("%s => %s %s" % (clip['src'], vo, vo.get('batch')))
|
||||||
for sub in vo.annotations.filter(layer="subtitles").exclude(value="").order_by("start"):
|
for sub in vo.annotations.filter(layer="subtitles").filter(languages=lang).exclude(value="").order_by("start"):
|
||||||
sdata = get_srt(sub, offset)
|
sdata = get_srt(sub, offset, tlang)
|
||||||
subs.append(sdata)
|
subs.append(sdata)
|
||||||
else:
|
else:
|
||||||
print("could not find vo for %s" % clip['src'])
|
print("could not find vo for %s" % clip['src'])
|
||||||
|
@ -656,8 +696,8 @@ def render_infinity(options):
|
||||||
if f.isdigit() and os.path.isdir(render_prefix + f) and state["offset"] > int(f) >= 100
|
if f.isdigit() and os.path.isdir(render_prefix + f) and state["offset"] > int(f) >= 100
|
||||||
]
|
]
|
||||||
if len(current) > state["max-items"]:
|
if len(current) > state["max-items"]:
|
||||||
current = list(reversed(ox.sorted_strings(current)))
|
current = ox.sorted_strings(current)
|
||||||
remove = list(reversed(current[-state["max-items"]:]))
|
remove = current[:-state["max-items"]]
|
||||||
update_m3u(render_prefix, exclude=remove)
|
update_m3u(render_prefix, exclude=remove)
|
||||||
for folder in remove:
|
for folder in remove:
|
||||||
folder = render_prefix + folder
|
folder = render_prefix + folder
|
||||||
|
|
|
@ -554,7 +554,6 @@ class KDEnliveProject:
|
||||||
] + value)
|
] + value)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def properties(self, *props):
|
def properties(self, *props):
|
||||||
return [
|
return [
|
||||||
self.get_element("property", attrib={"name": name}, text=str(value) if value is not None else value)
|
self.get_element("property", attrib={"name": name}, text=str(value) if value is not None else value)
|
||||||
|
|
6
sax.py
6
sax.py
|
@ -31,7 +31,7 @@ reverb = {
|
||||||
"src": reverb_wav,
|
"src": reverb_wav,
|
||||||
"duration": 3600.0,
|
"duration": 3600.0,
|
||||||
"filter": {
|
"filter": {
|
||||||
"volume": "0.5"
|
"volume": "3.5"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,14 +39,14 @@ long = {
|
||||||
"src": long_wav,
|
"src": long_wav,
|
||||||
"duration": 3600.0,
|
"duration": 3600.0,
|
||||||
"filter": {
|
"filter": {
|
||||||
"volume": "-4"
|
"volume": "-1"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
noise = {
|
noise = {
|
||||||
"src": nois_wav,
|
"src": nois_wav,
|
||||||
"duration": 3600.0,
|
"duration": 3600.0,
|
||||||
"filter": {
|
"filter": {
|
||||||
"volume": "4.75"
|
"volume": "7.75"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
title.png
Normal file
BIN
title.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
Loading…
Reference in a new issue