diff --git a/align_subtitles.py b/align_subtitles.py
index f12816a..491ec75 100644
--- a/align_subtitles.py
+++ b/align_subtitles.py
@@ -21,7 +21,7 @@ https://textb.org/r/t_for_time_subtitles_5_ashley/
data = requests.get(url).text
parts = data.strip().split('##')
print(url)
- prefix = '/srv/p_for_power/vo/' + url.split('/')[-2].split('subtitles_')[-1]
+ prefix = '/srv/t_for_time/vo/' + url.split('/')[-2].split('subtitles_')[-1]
for part in parts:
part = part.strip().split('\n')
if part:
@@ -86,7 +86,7 @@ def update_subtitles():
wav = i.files.filter(selected=True)[0].data.path
id = i.get('title').split('_')[0]
batch = i.get('batch')[0][5:].lower().replace('-', '_').replace(' ', '')
- txt = '/srv/p_for_power/vo/%s_%s.txt' % (batch, id)
+ txt = '/srv/t_for_time/vo/%s_%s.txt' % (batch, id)
if os.path.exists(txt):
print(i, wav, txt)
subtitles = gentle2subtitles(align_text(txt, wav))
diff --git a/config.jsonc b/config.jsonc
index d295fbc..58cc505 100644
--- a/config.jsonc
+++ b/config.jsonc
@@ -1015,7 +1015,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
the system (from).
*/
"site": {
- "description": "P for Power - pan.do/ra",
+ "description": "This is a demo of pan.do/ra - a free, open source media archive. It allows you to manage large, decentralized collections of video, to collaboratively create metadata and time-based annotations, and to serve your archive as a cutting-edge web application.",
"email": {
// E-mail address in contact form (to)
"contact": "system@time.0x2620.org",
@@ -1025,12 +1025,12 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
"system": "system@time.0x2620.org"
},
"https": true,
- "id": "p_for_power",
- "name": "P for Power",
+ "id": "t_for_time",
+ "name": "T for Time",
// Set to true to allow search engines to index the site
"public": false,
"sendReferrer": true,
- "url": "power.0x2620.org"
+ "url": "time.0x2620.org"
},
/*
"sitePages" defines the sections of the main site dialog. If "news" is
@@ -1052,6 +1052,9 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
cross-instance references.
*/
"sites": [
+ {"name": "0xDB", "url": "0xdb.org", "https": true},
+ {"name": "Pad.ma", "url": "pad.ma", "https": true},
+ {"name": "Indiancine.ma", "url": "indiancine.ma", "https": true}
],
/*
"textRightsLevels" defines a list of rights levels for texts.
diff --git a/install.py b/install.py
index 20a4484..1ad6f4d 100755
--- a/install.py
+++ b/install.py
@@ -100,7 +100,7 @@ if os.path.exists('__init__.py'):
local_settings += '\nLOCAL_APPS = ["%s"]\n' % name
local_settings_changed = True
else:
- apps = re.compile(r'(LOCAL_APPS.*?)\]', re.DOTALL).findall(local_settings)[0]
+ apps = re.compile('(LOCAL_APPS.*?)\]', re.DOTALL).findall(local_settings)[0]
if name not in apps:
new_apps = apps.strip() + ',\n"%s"\n' % name
local_settings = local_settings.replace(apps, new_apps)
diff --git a/management/commands/generate_clips.py b/management/commands/generate_clips.py
index c0a16df..4c96377 100644
--- a/management/commands/generate_clips.py
+++ b/management/commands/generate_clips.py
@@ -1,13 +1,155 @@
-from django.core.management.base import BaseCommand
+import json
+import os
+import re
+import subprocess
+from collections import defaultdict
-from ...render import generate_clips, default_prefix
+from django.core.management.base import BaseCommand
+from django.conf import settings
+
+import item.models
+import itemlist.models
+
+from ...render import get_srt
+
+
+def resolve_roman(s):
+ extra = re.compile('^\d+(.*?)$').findall(s)
+ if extra:
+ extra = extra[0].lower()
+ new = {
+ 'i': '1', 'ii': '2', 'iii': '3', 'iv': '4', 'v': '5',
+ 'vi': '6', 'vii': 7, 'viii': '8', 'ix': '9', 'x': '10'
+ }.get(extra, extra)
+ return s.replace(extra, new)
+ return s
+
+def format_duration(duration, fps):
+ return float('%0.5f' % (round(duration * fps) / fps))
class Command(BaseCommand):
help = 'generate symlinks to clips and clips.json'
def add_arguments(self, parser):
- parser.add_argument('--prefix', action='store', dest='prefix', default=default_prefix, help='prefix to build clips in')
+ 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('--censored', action='store', dest='censored', default=None, help='censor items from list')
def handle(self, **options):
- return generate_clips(options)
+ 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
+ if options['censored']:
+ censored_list = itemlist.models.List.get(options["censored"])
+ censored = list(censored_list.get_items(censored_list.user).all().values_list('public_id', flat=True))
+ clips = []
+ for i in item.models.Item.objects.filter(sort__type='original'):
+ original_target = ""
+ qs = item.models.Item.objects.filter(data__title=i.data['title']).exclude(id=i.id)
+ if qs.count() >= 1:
+ clip = {}
+ durations = []
+ for e in item.models.Item.objects.filter(data__title=i.data['title']):
+ if 'type' not in e.data:
+ print("ignoring invalid video %s (no type)" % e)
+ continue
+ if not e.files.filter(selected=True).exists():
+ continue
+ source = e.files.filter(selected=True)[0].data.path
+ ext = os.path.splitext(source)[1]
+ type_ = e.data['type'][0].lower()
+ target = os.path.join(prefix, type_, i.data['title'] + ext)
+ os.makedirs(os.path.dirname(target), exist_ok=True)
+ if os.path.islink(target):
+ os.unlink(target)
+ os.symlink(source, target)
+ if type_ == "original":
+ original_target = target
+ if options['censored'] and e.public_id in censored:
+ target = '/srv/t_for_time/censored.mp4'
+ clip[type_] = target
+ durations.append(e.files.filter(selected=True)[0].duration)
+ clip["duration"] = min(durations)
+ if not clip["duration"]:
+ print('!!', durations, clip)
+ continue
+ cd = format_duration(clip["duration"], 24)
+ #if cd != clip["duration"]:
+ # print(clip["duration"], '->', cd, durations, clip)
+ clip["duration"] = cd
+ clip['tags'] = i.data.get('tags', [])
+ clip['editingtags'] = i.data.get('editingtags', [])
+ name = os.path.basename(original_target)
+ seqid = re.sub("Hotel Aporia_(\d+)", "S\\1_", name)
+ seqid = re.sub("Night March_(\d+)", "S\\1_", seqid)
+ seqid = re.sub("_(\d+)H_(\d+)", "_S\\1\\2_", seqid)
+ seqid = seqid.split('_')[:2]
+ seqid = [b[1:] if b[0] in ('B', 'S') else '0' for b in seqid]
+ seqid[1] = resolve_roman(seqid[1])
+ seqid[1] = ''.join([b for b in seqid[1] if b.isdigit()])
+ if not seqid[1]:
+ seqid[1] = '0'
+ try:
+ clip['seqid'] = int(''.join(['%06d' % int(b) for b in seqid]))
+ except:
+ print(name, seqid, 'failed')
+ raise
+ if "original" in clip and "foreground" in clip and "background" in clip:
+ clips.append(clip)
+ elif "original" in clip and "animation" in clip:
+ clips.append(clip)
+ else:
+ print("ignoring incomplete video", i)
+
+ with open(os.path.join(prefix, 'clips.json'), 'w') as fd:
+ json.dump(clips, fd, indent=2, ensure_ascii=False)
+
+ print("using", len(clips), "clips")
+
+ voice_over = defaultdict(dict)
+ for vo in item.models.Item.objects.filter(
+ data__type__contains="Voice Over",
+ ):
+ fragment_id = int(vo.get('title').split('_')[0])
+ source = vo.files.filter(selected=True)[0]
+ batch = vo.get('batch')[0].replace('Text-', '')
+ src = source.data.path
+ target = os.path.join(prefix, 'voice_over', batch, '%s.wav' % fragment_id)
+ os.makedirs(os.path.dirname(target), exist_ok=True)
+ if os.path.islink(target):
+ os.unlink(target)
+ os.symlink(src, target)
+ subs = []
+ for sub in vo.annotations.filter(layer="subtitles", languages=lang).exclude(value="").order_by("start"):
+ sdata = get_srt(sub, lang=tlang)
+ subs.append(sdata)
+ voice_over[fragment_id][batch] = {
+ "src": target,
+ "duration": format_duration(source.duration, 24),
+ "subs": subs
+ }
+ with open(os.path.join(prefix, 'voice_over.json'), 'w') as fd:
+ json.dump(voice_over, fd, indent=2, ensure_ascii=False)
+
+ if options['censored']:
+ censored_mp4 = '/srv/t_for_time/censored.mp4'
+ if not os.path.exists(censored_mp4):
+ cmd = [
+ "ffmpeg",
+ "-nostats", "-loglevel", "error",
+ "-f", "lavfi",
+ "-i", "color=color=white:size=1920x1080:rate=24",
+ "-t", "3600",
+ "-c:v", "libx264",
+ "-pix_fmt", "yuv420p",
+ censored_mp4
+ ]
+ subprocess.call(cmd)
diff --git a/management/commands/infinity.py b/management/commands/infinity.py
index 64ea7af..0ca70fa 100644
--- a/management/commands/infinity.py
+++ b/management/commands/infinity.py
@@ -5,14 +5,14 @@ import subprocess
from django.core.management.base import BaseCommand
from django.conf import settings
-from ...render import render_infinity, default_prefix
+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=default_prefix, 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('--config', action='store', dest='config', default=None, help='config')
parser.add_argument('--duration', action='store', dest='duration', default="3600", help='target duration of all fragments in seconds')
parser.add_argument('--single-file', action='store_true', dest='single_file', default=False, help='render to single video')
diff --git a/management/commands/render.py b/management/commands/render.py
index b4a242c..91e8fa5 100644
--- a/management/commands/render.py
+++ b/management/commands/render.py
@@ -5,14 +5,14 @@ import subprocess
from django.core.management.base import BaseCommand
from django.conf import settings
-from ...render import render_all, default_prefix
+from ...render import render_all
class Command(BaseCommand):
help = 'genrate kdenlive porject and render'
def add_arguments(self, parser):
- parser.add_argument('--prefix', action='store', dest='prefix', default=default_prefix, 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('--no-video', action='store_true', dest='no_video', default=False, help='don\'t render video')
diff --git a/management/commands/update_subtitles.py b/management/commands/update_subtitles.py
index e103375..a1482d6 100644
--- a/management/commands/update_subtitles.py
+++ b/management/commands/update_subtitles.py
@@ -5,15 +5,16 @@ import subprocess
from django.core.management.base import BaseCommand
from django.conf import settings
-from ...render import update_subtitles, default_prefix
+from ...render import update_subtitles
class Command(BaseCommand):
help = 'genrate kdenlive porject and render'
def add_arguments(self, parser):
- parser.add_argument('--prefix', action='store', dest='prefix', default=default_prefix, 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('--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):
update_subtitles(options)
diff --git a/player/play-back.desktop b/player/play-back.desktop
index 8f10cd9..e6d630f 100644
--- a/player/play-back.desktop
+++ b/player/play-back.desktop
@@ -1,6 +1,6 @@
[Desktop Entry]
Type=Application
-Exec=/srv/pandora/p_for_power/player/player.py --mode peer --playlist /srv/p_for_power/render/back.m3u
+Exec=/srv/pandora/t_for_time/player/player.py --mode peer --playlist /srv/t_for_time/render/back.m3u
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
diff --git a/player/play-front.desktop b/player/play-front.desktop
index 5d2337c..6f95628 100644
--- a/player/play-front.desktop
+++ b/player/play-front.desktop
@@ -1,6 +1,6 @@
[Desktop Entry]
Type=Application
-Exec=/srv/pandora/p_for_power/player/player.py --mode main --playlist /srv/p_for_power/render/front.m3u
+Exec=/srv/pandora/t_for_time/player/player.py --mode main --playlist /srv/t_for_time/render/front.m3u
Hidden=false
NoDisplay=false
X-GNOME-Autostart-enabled=true
diff --git a/player/player-back.service b/player/player-back.service
index 8a19a62..4804318 100644
--- a/player/player-back.service
+++ b/player/player-back.service
@@ -7,7 +7,7 @@ Wants=network-online.target
Type=simple
Restart=on-failure
KillSignal=SIGINT
-ExecStart=/srv/pandora/p_for_power/player/player.py --mode peer --playlist /srv/p_for_power/render/back.m3u --config /srv/p_for_power/render/back.json
+ExecStart=/srv/pandora/t_for_time/player/player.py --mode peer --playlist /srv/t_for_time/render/back.m3u
[Install]
WantedBy=graphical-session.target
diff --git a/player/player-front.service b/player/player-front.service
index 17f998b..7470316 100644
--- a/player/player-front.service
+++ b/player/player-front.service
@@ -6,7 +6,7 @@ After=gnome-session.target network-online.target
Type=simple
Restart=on-failure
KillSignal=SIGINT
-ExecStart=/srv/pandora/p_for_power/player/player.py --mode main --playlist /srv/p_for_power/render/front.m3u --config /srv/p_for_power/render/front.json
+ExecStart=/srv/pandora/t_for_time/player/player.py --mode main --playlist /srv/t_for_time/render/front.m3u
[Install]
WantedBy=graphical-session.target
diff --git a/player/player.py b/player/player.py
index fa3d548..28b41e0 100755
--- a/player/player.py
+++ b/player/player.py
@@ -13,7 +13,7 @@ import mpv
import logging
-logger = logging.getLogger('p_for_power')
+logger = logging.getLogger('t_for_time')
SYNC_TOLERANCE = 0.05
SYNC_GRACE_TIME = 5
@@ -74,7 +74,7 @@ class Sync(Thread):
input_vo_keyboard=True,
)
self.sax.loop_file = True
- self.sax.play("/srv/p_for_power/render/Saxophone-5.1.mp4")
+ self.sax.play("/srv/t_for_time/render/Saxophone-5.1.mp4")
else:
self.sax = None
@@ -408,11 +408,11 @@ class Sync(Thread):
def main():
- prefix = os.path.expanduser('~/Videos/p_for_power')
+ prefix = os.path.expanduser('~/Videos/t_for_time')
- parser = argparse.ArgumentParser(description='p_for_power sync player')
+ parser = argparse.ArgumentParser(description='t_for_time sync player')
parser.add_argument('--mode', help='peer or main', default="peer")
- parser.add_argument('--playlist', default='/srv/p_for_power/render/128/front.m3u', help="m3u")
+ 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)
parser.add_argument('--debug', action='store_true', help='debug', default=False)
diff --git a/player/saxophone-loop.desktop b/player/saxophone-loop.desktop
new file mode 100644
index 0000000..7b31def
--- /dev/null
+++ b/player/saxophone-loop.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Type=Application
+Exec=/usr/bin/mpv --quiet --loop /srv/t_for_time/render/Saxophone-5.1.mp4
+Hidden=false
+NoDisplay=false
+X-GNOME-Autostart-enabled=true
+Name=loop
+Comment=
diff --git a/player/saxophone.service b/player/saxophone.service
new file mode 100644
index 0000000..2b9d2f0
--- /dev/null
+++ b/player/saxophone.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=saxophone loop
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/mpv --quiet --loop /srv/t_for_time/render/Saxophone-5.1.mp4
+KillSignal=SIGINT
+
+[Install]
+WantedBy=graphical-session.target
diff --git a/render.py b/render.py
index cbf8828..56fc94a 100644
--- a/render.py
+++ b/render.py
@@ -16,7 +16,6 @@ import lxml.etree
from .pi import random
from .render_kdenlive import KDEnliveProject, _CACHE, melt_xml, get_melt
-default_prefix = "/srv/p_for_power"
def random_int(seq, length):
n = n_ = length - 1
@@ -62,7 +61,7 @@ def write_if_new(path, data, mode=''):
old = ""
is_new = data != old
if path.endswith(".kdenlive"):
- is_new = re.sub(r'\{.{36}\}', '', data) != re.sub(r'\{.{36}\}', '', old)
+ is_new = re.sub('\{.{36}\}', '', data) != re.sub('\{.{36}\}', '', old)
if is_new:
with open(path, write_mode) as fd:
fd.write(data)
@@ -372,33 +371,17 @@ def get_offset_duration(prefix):
duration += get_scene_duration(scene)
return duration
-def write_subtitles(data, folder, options):
- data = fix_overlaps(data)
- path = folder / "front.srt"
- if options.get("subtitle_format") == "srt":
- srt = ox.srt.encode(data)
- write_if_new(str(path), srt, 'b')
- path = folder / "front.ass"
- if os.path.exists(path):
- os.unlink(path)
- else:
- if os.path.exists(path):
- os.unlink(path)
- path = folder / "front.ass"
- ass = ass_encode(data, options)
- write_if_new(str(path), ass, '')
-
-
def render(root, scene, prefix='', options=None):
- if options is None:
- options = {}
+ if options is None: options = {}
fps = 24
files = []
scene_duration = int(get_scene_duration(scene) * fps)
for timeline, data in scene.items():
if timeline == "subtitles":
- folder = Path(root) / prefix
- write_subtitles(data, folder, options)
+ path = os.path.join(root, prefix + "front.srt")
+ data = fix_overlaps(data)
+ srt = ox.srt.encode(data)
+ write_if_new(path, srt, 'b')
continue
#print(timeline)
project = KDEnliveProject(root)
@@ -486,32 +469,15 @@ def get_fragments(clips, voice_over, prefix):
fragment['clips'] = []
for clip in clips:
#if set(clip['tags']) & set(fragment['tags']) and not set(clip['tags']) & set(fragment['anti-tags']):
- key = 'original'
- original = clip['original']
- if 'original_censored' in clip:
- original = clip['original_censored']
- if original in originals:
+ if clip['original'] in originals:
fragment['clips'].append(clip)
fragment["voice_over"] = voice_over.get(str(fragment["id"]), {})
fragments.append(fragment)
fragments.sort(key=lambda f: ox.sort_string(f['name']))
return fragments
-def parse_lang(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
- return lang, tlang
-
def render_all(options):
- options = load_defaults(options)
prefix = options['prefix']
duration = int(options['duration'])
base = int(options['offset'])
@@ -801,7 +767,7 @@ def render_all(options):
fn = base_prefix / fn
if os.path.exists(fn):
os.unlink(fn)
- join_subtitles(base_prefix, options)
+ join_subtitles(base_prefix)
print("Duration - Target: %s Actual: %s" % (target_position, position))
print(json.dumps(dict(stats), sort_keys=True, indent=2))
@@ -824,39 +790,11 @@ def add_translations(sub, lang):
value += '\n' + tvalue
return value
-def add_translations_dict(sub, langs):
- values = {}
- value = sub.value.replace('
', '
').replace('
\n', '\n').replace('
', '\n').strip()
- if sub.languages:
- value = ox.strip_tags(value)
- values[sub.languages] = value
- else:
- values["en"] = value
- for slang in langs:
- slang_value = None if slang == "en" else slang
- if sub.languages == slang_value:
- continue
-
- for tsub in sub.item.annotations.filter(
- layer="subtitles", start=sub.start, end=sub.end,
- languages=slang_value
- ):
- tvalue = tsub.value.replace('
', '
').replace('
\n', '\n').replace('
', '\n').strip()
- if tsub.languages:
- tvalue = ox.strip_tags(tvalue)
- values[slang] = tvalue
- return values
-
-
-def get_srt(sub, offset, lang, tlang):
+def get_srt(sub, offset=0, lang=None):
sdata = sub.json(keys=['in', 'out', 'value'])
sdata['value'] = sdata['value'].replace('
', '
').replace('
\n', '\n').replace('
', '\n').strip()
- if tlang:
- sdata['value'] = add_translations(sub, tlang)
- langs = [lang]
- if tlang:
- langs += tlang
- sdata['values'] = add_translations_dict(sub, langs)
+ if lang:
+ sdata['value'] = add_translations(sub, lang)
if offset:
sdata["in"] += offset
sdata["out"] += offset
@@ -873,55 +811,21 @@ def fix_overlaps(data):
previous = sub
return data
-def shift_clips(data, offset):
- for clip in data:
- clip['in'] += offset
- clip['out'] += offset
-
-def scene_subtitles(scene, options):
- import item.models
- offset = 0
- subs = []
- lang, tlang = parse_lang(options["lang"])
- for clip in scene['audio-center']['A1']:
- if not clip.get("blank"):
- batch, fragment_id = clip['src'].replace('.wav', '').split('/')[-2:]
- vo = item.models.Item.objects.filter(
- data__batch__icontains=batch, data__title__startswith=fragment_id + '_'
- ).first()
- if vo:
- #print("%s => %s %s" % (clip['src'], vo, vo.get('batch')))
- for sub in vo.annotations.filter(
- layer="subtitles"
- ).filter(
- languages=None if lang == "en" else lang
- ).exclude(value="").order_by("start"):
- sdata = get_srt(sub, offset, lang, tlang)
- subs.append(sdata)
- else:
- print("could not find vo for %s" % clip['src'])
- offset += clip['duration']
- return subs
-
-
-def load_defaults(options):
- path = os.path.join(options["prefix"], "options.json")
- if os.path.exists(path):
- with open(path) as fd:
- defaults = json.load(fd)
- for key in defaults:
- if key not in options:
- options[key] = defaults[key]
- return options
-
-
def update_subtitles(options):
import item.models
- options = load_defaults(options)
prefix = Path(options['prefix'])
base = int(options['offset'])
- lang, tlang = parse_lang(options["lang"])
+ 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")
if os.path.exists(_cache):
@@ -936,59 +840,25 @@ def update_subtitles(options):
continue
with open(scene_json) as fd:
scene = json.load(fd)
- subs = scene_subtitles(scene, options)
- write_subtitles(subs, folder, options)
+ offset = 0
+ subs = []
+ for clip in scene['audio-center']['A1']:
+ if not clip.get("blank"):
+ batch, fragment_id = clip['src'].replace('.wav', '').split('/')[-2:]
+ vo = item.models.Item.objects.filter(data__batch__icontains=batch, data__title__startswith=fragment_id + '_').first()
+ if vo:
+ #print("%s => %s %s" % (clip['src'], vo, vo.get('batch')))
+ for sub in vo.annotations.filter(layer="subtitles").filter(languages=lang).exclude(value="").order_by("start"):
+ sdata = get_srt(sub, offset, tlang)
+ subs.append(sdata)
+ else:
+ print("could not find vo for %s" % clip['src'])
+ offset += clip['duration']
+ path = folder / "front.srt"
+ data = fix_overlaps(subs)
+ srt = ox.srt.encode(subs)
+ write_if_new(str(path), srt, 'b')
-def ass_encode(subs, options):
- if "lang" in options:
- langs = options["lang"].split(',')
- else:
- langs = list(subs[0]["values"])
- #print('ass_encode', langs, options)
- #print(subs)
-
- header = '''[Script Info]
-ScriptType: v4.00+
-PlayResX: 1920
-PlayResY: 1080
-ScaledBorderAndShadow: yes
-YCbCr Matrix: None
-
-[V4+ Styles]
-Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
-'''
- ass = header
- offset = options.get("sub_margin", 10)
- spacing = options.get("sub_spacing", 20)
- height = 42
- styles = []
- for lang in reversed(langs):
- if isinstance(options.get("font"), list) and lang in options["font"]:
- font = options["font"][lang]
- else:
- font = 'SimHei' if lang in ('zh', 'jp') else 'Menlo'
- if isinstance(options.get("font_size"), list) and lang in options["font_size"]:
- size = options["font_size"][lang]
- else:
- size = 46 if font == 'SimHei' else 42
-
- styles.append(
- f'Style: {lang},{font},{size},&Hffffff,&Hffffff,&H0,&H0,0,0,0,0,100,100,0,0,1,1,0,2,10,10,{offset},1'
- )
- offset += size + spacing
- ass += '\n'.join(reversed(styles)) + '\n'
- events = [
- 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text'
- ]
- for sub in subs:
- start = ox.format_timecode(sub['in']).rstrip('0')
- stop = ox.format_timecode(sub['out']).rstrip('0')
- for lang in reversed(langs):
- value = sub['values'][lang]
- event = f'Dialogue: 0,{start},{stop},{lang},,0,0,0,,{value}'
- events.append(event)
- ass += '\n\n[Events]\n' + '\n'.join(events) + '\n'
- return ass
def update_m3u(render_prefix, exclude=[]):
files = ox.sorted_strings(glob(render_prefix + "*/*/back.mp4"))
@@ -1061,8 +931,7 @@ def render_infinity(options):
shutil.move(state_f + "~", state_f)
-def join_subtitles(base_prefix, options):
- '''
+def join_subtitles(base_prefix):
subtitles = list(sorted(glob('%s/*/front.srt' % base_prefix)))
data = []
position = 0
@@ -1072,142 +941,3 @@ def join_subtitles(base_prefix, options):
position += get_scene_duration(scene)
with open(base_prefix / 'front.srt', 'wb') as fd:
fd.write(ox.srt.encode(data))
- '''
- scenes = list(sorted(glob('%s/*/scene.json' % base_prefix)))
- data = []
- position = 0
- for scene in scenes:
- subs = scene_subtitles(scene, options)
- data += shift_clips(subs, position)
- position += get_scene_duration(scene)
- write_subtitles(data, base_prefix, options)
-
-def resolve_roman(s):
- extra = re.compile(r'^\d+(.*?)$').findall(s)
- if extra:
- extra = extra[0].lower()
- new = {
- 'i': '1', 'ii': '2', 'iii': '3', 'iv': '4', 'v': '5',
- 'vi': '6', 'vii': 7, 'viii': '8', 'ix': '9', 'x': '10'
- }.get(extra, extra)
- return s.replace(extra, new)
- return s
-
-def generate_clips(options):
- import item.models
- import itemlist.models
-
- options = load_defaults(options)
- prefix = options['prefix']
- lang, tlang = parse_lang(options["lang"])
- if options['censored']:
- censored_list = itemlist.models.List.get(options["censored"])
- censored = list(censored_list.get_items(
- censored_list.user
- ).all().values_list('public_id', flat=True))
- clips = []
- for i in item.models.Item.objects.filter(sort__type='original'):
- original_target = ""
- qs = item.models.Item.objects.filter(data__title=i.data['title']).exclude(id=i.id)
- if qs.count() >= 1:
- clip = {}
- durations = []
- for e in item.models.Item.objects.filter(data__title=i.data['title']):
- if 'type' not in e.data:
- print("ignoring invalid video %s (no type)" % e)
- continue
- if not e.files.filter(selected=True).exists():
- continue
- source = e.files.filter(selected=True)[0].data.path
- ext = os.path.splitext(source)[1]
- type_ = e.data['type'][0].lower()
- target = os.path.join(prefix, type_, i.data['title'] + ext)
- os.makedirs(os.path.dirname(target), exist_ok=True)
- if os.path.islink(target):
- os.unlink(target)
- os.symlink(source, target)
- if type_ == "original":
- original_target = target
- if options['censored'] and e.public_id in censored:
- clip[type_ + "_censored"] = target
- target = '/srv/t_for_time/censored.mp4'
- clip[type_] = target
- durations.append(e.files.filter(selected=True)[0].duration)
- clip["duration"] = min(durations)
- if not clip["duration"]:
- print('!!', durations, clip)
- continue
- cd = format_duration(clip["duration"], 24)
- #if cd != clip["duration"]:
- # print(clip["duration"], '->', cd, durations, clip)
- clip["duration"] = cd
- clip['tags'] = i.data.get('tags', [])
- clip['editingtags'] = i.data.get('editingtags', [])
- name = os.path.basename(original_target)
- seqid = re.sub(r"Hotel Aporia_(\d+)", "S\\1_", name)
- seqid = re.sub(r"Night March_(\d+)", "S\\1_", seqid)
- seqid = re.sub(r"_(\d+)H_(\d+)", "_S\\1\\2_", seqid)
- seqid = seqid.split('_')[:2]
- seqid = [b[1:] if b[0] in ('B', 'S') else '0' for b in seqid]
- seqid[1] = resolve_roman(seqid[1])
- seqid[1] = ''.join([b for b in seqid[1] if b.isdigit()])
- if not seqid[1]:
- seqid[1] = '0'
- try:
- clip['seqid'] = int(''.join(['%06d' % int(b) for b in seqid]))
- except:
- print(name, seqid, 'failed')
- raise
- if "original" in clip and "foreground" in clip and "background" in clip:
- clips.append(clip)
- elif "original" in clip and "animation" in clip:
- clips.append(clip)
- else:
- print("ignoring incomplete video", i)
-
- with open(os.path.join(prefix, 'clips.json'), 'w') as fd:
- json.dump(clips, fd, indent=2, ensure_ascii=False)
-
- print("using", len(clips), "clips")
-
- voice_over = defaultdict(dict)
- for vo in item.models.Item.objects.filter(
- data__type__contains="Voice Over",
- ):
- fragment_id = int(vo.get('title').split('_')[0])
- source = vo.files.filter(selected=True)[0]
- batch = vo.get('batch')[0].replace('Text-', '')
- src = source.data.path
- target = os.path.join(prefix, 'voice_over', batch, '%s.wav' % fragment_id)
- os.makedirs(os.path.dirname(target), exist_ok=True)
- if os.path.islink(target):
- os.unlink(target)
- os.symlink(src, target)
- subs = []
- for sub in vo.annotations.filter(
- layer="subtitles", languages=lang
- ).exclude(value="").order_by("start"):
- sdata = get_srt(sub, 0, lang, tlang)
- subs.append(sdata)
- voice_over[fragment_id][batch] = {
- "src": target,
- "duration": format_duration(source.duration, 24),
- "subs": subs
- }
- with open(os.path.join(prefix, 'voice_over.json'), 'w') as fd:
- json.dump(voice_over, fd, indent=2, ensure_ascii=False)
-
- if options['censored']:
- censored_mp4 = '/srv/t_for_time/censored.mp4'
- if not os.path.exists(censored_mp4):
- cmd = [
- "ffmpeg",
- "-nostats", "-loglevel", "error",
- "-f", "lavfi",
- "-i", "color=color=white:size=1920x1080:rate=24",
- "-t", "3600",
- "-c:v", "libx264",
- "-pix_fmt", "yuv420p",
- censored_mp4
- ]
- subprocess.call(cmd)
diff --git a/sax.py b/sax.py
new file mode 100644
index 0000000..c1e916b
--- /dev/null
+++ b/sax.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python3
+import os
+from render_kdenlive import KDEnliveProject, _CACHE
+import subprocess
+
+def generate_sax_mix(root):
+ os.chdir(root)
+
+
+root = os.path.abspath(".")
+
+long_wav = "Soon_Kim_Long_Reverb_Only2.wav"
+nois_wav = "Soon_Kim_Noise.wav"
+reverb_wav = "Soon_Kim_Short_Reverb_Mix2.wav"
+'''
+i = item.models.Item.objects.get(data__title='Soon_Kim_Long_Reverb_Only2')
+i.files.all()[0].data.path
+'/srv/pandora/data/media/6b/44/16/3f2905e886/data.wav'
+
+i = item.models.Item.objects.get(data__title='Soon_Kim_Short_Reverb_Mix2')
+i.files.all()[0].data.path
+'/srv/pandora/data/media/ee/e0/04/d4ab42c3de/data.wav'
+
+i = item.models.Item.objects.get(data__title='Soon_Kim_Noise')
+i.files.all()[0].data.path
+'/srv/pandora/data/media/84/88/87/d2fb2e2dc2/data.wav'
+'''
+
+
+reverb = {
+ "src": reverb_wav,
+ "duration": 3600.0,
+ "filter": {
+ "volume": "3.5"
+ },
+}
+
+long = {
+ "src": long_wav,
+ "duration": 3600.0,
+ "filter": {
+ "volume": "-1"
+ },
+}
+noise = {
+ "src": nois_wav,
+ "duration": 3600.0,
+ "filter": {
+ "volume": "7.75"
+ },
+}
+
+project = KDEnliveProject(root)
+project.append_clip('A1', long)
+project.append_clip('A2', noise)
+path = os.path.join(root, "sax-mix.kdenlive")
+with open(path, 'w') as fd:
+ fd.write(project.to_xml())
+
+project = KDEnliveProject(root)
+project.append_clip('A1', reverb)
+path = os.path.join(root, "sax-reverb-mix.kdenlive")
+with open(path, 'w') as fd:
+ fd.write(project.to_xml())
+
+cmds = []
+cmds.append([
+ "melt", "sax-mix.kdenlive", '-quiet', '-consumer', 'avformat:sax-mix.wav'
+])
+cmds.append([
+ "melt", "sax-reverb-mix.kdenlive", '-quiet', '-consumer', 'avformat:sax-reverb-mix.wav'
+])
+cmds.append([
+ "ffmpeg", "-y",
+ "-nostats", "-loglevel", "error",
+ "-f", "lavfi", "-i", "anullsrc=r=48000:cl=mono", "-t", "3600", "silence.wav"
+])
+
+for src, out1, out2 in (
+ ('sax-reverb-mix.wav', "fl.wav", "fr.wav"),
+ ("sax-mix.wav", "bl.wav", "br.wav"),
+):
+ cmds.append([
+ "ffmpeg", "-y",
+ "-nostats", "-loglevel", "error",
+ "-i", src,
+ "-filter_complex",
+ "[0:0]pan=1|c0=c0[left]; [0:0]pan=1|c0=c1[right]",
+ "-map", "[left]", out1,
+ "-map", "[right]", out2,
+ ])
+
+cmds.append([
+ "ffmpeg", "-y",
+ "-nostats", "-loglevel", "error",
+ "-i", "fl.wav",
+ "-i", "fr.wav",
+ "-i", "silence.wav",
+ "-i", "silence.wav",
+ "-i", "bl.wav",
+ "-i", "br.wav",
+ "-filter_complex", "[0:a][1:a][2:a][3:a][4:a][5:a]amerge=inputs=6[a]",
+ "-map", "[a]",
+ "-ar", "48000",
+ "-c:a", "aac", "Saxophone-5.1.mp4"
+])
+for cmd in cmds:
+ print(" ".join([str(x) for x in cmd]))
+ subprocess.call(cmd)
diff --git a/static/js/infoView.p_for_power.js b/static/js/infoView.t_for_time.js
similarity index 100%
rename from static/js/infoView.p_for_power.js
rename to static/js/infoView.t_for_time.js