Compare commits
No commits in common. "d4372d05957d25796879399111b6f5bc53f98751" and "454c4eeaa5884ef7e7523b43f78c3f560826cafa" have entirely different histories.
d4372d0595
...
454c4eeaa5
17 changed files with 340 additions and 336 deletions
|
|
@ -21,7 +21,7 @@ https://textb.org/r/t_for_time_subtitles_5_ashley/
|
||||||
data = requests.get(url).text
|
data = requests.get(url).text
|
||||||
parts = data.strip().split('##')
|
parts = data.strip().split('##')
|
||||||
print(url)
|
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:
|
for part in parts:
|
||||||
part = part.strip().split('\n')
|
part = part.strip().split('\n')
|
||||||
if part:
|
if part:
|
||||||
|
|
@ -86,7 +86,7 @@ def update_subtitles():
|
||||||
wav = i.files.filter(selected=True)[0].data.path
|
wav = i.files.filter(selected=True)[0].data.path
|
||||||
id = i.get('title').split('_')[0]
|
id = i.get('title').split('_')[0]
|
||||||
batch = i.get('batch')[0][5:].lower().replace('-', '_').replace(' ', '')
|
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):
|
if os.path.exists(txt):
|
||||||
print(i, wav, txt)
|
print(i, wav, txt)
|
||||||
subtitles = gentle2subtitles(align_text(txt, wav))
|
subtitles = gentle2subtitles(align_text(txt, wav))
|
||||||
|
|
|
||||||
11
config.jsonc
11
config.jsonc
|
|
@ -1015,7 +1015,7 @@ examples (config.SITENAME.jsonc) that are part of this pan.do/ra distribution.
|
||||||
the system (from).
|
the system (from).
|
||||||
*/
|
*/
|
||||||
"site": {
|
"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": {
|
"email": {
|
||||||
// E-mail address in contact form (to)
|
// E-mail address in contact form (to)
|
||||||
"contact": "system@time.0x2620.org",
|
"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"
|
"system": "system@time.0x2620.org"
|
||||||
},
|
},
|
||||||
"https": true,
|
"https": true,
|
||||||
"id": "p_for_power",
|
"id": "t_for_time",
|
||||||
"name": "P for Power",
|
"name": "T for Time",
|
||||||
// Set to true to allow search engines to index the site
|
// Set to true to allow search engines to index the site
|
||||||
"public": false,
|
"public": false,
|
||||||
"sendReferrer": true,
|
"sendReferrer": true,
|
||||||
"url": "power.0x2620.org"
|
"url": "time.0x2620.org"
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
"sitePages" defines the sections of the main site dialog. If "news" is
|
"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.
|
cross-instance references.
|
||||||
*/
|
*/
|
||||||
"sites": [
|
"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.
|
"textRightsLevels" defines a list of rights levels for texts.
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ if os.path.exists('__init__.py'):
|
||||||
local_settings += '\nLOCAL_APPS = ["%s"]\n' % name
|
local_settings += '\nLOCAL_APPS = ["%s"]\n' % name
|
||||||
local_settings_changed = True
|
local_settings_changed = True
|
||||||
else:
|
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:
|
if name not in apps:
|
||||||
new_apps = apps.strip() + ',\n"%s"\n' % name
|
new_apps = apps.strip() + ',\n"%s"\n' % name
|
||||||
local_settings = local_settings.replace(apps, new_apps)
|
local_settings = local_settings.replace(apps, new_apps)
|
||||||
|
|
|
||||||
|
|
@ -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):
|
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('--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):
|
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)
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import subprocess
|
||||||
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 render_infinity, default_prefix
|
from ...render import render_infinity
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'render infinity'
|
help = 'render infinity'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
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('--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('--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')
|
parser.add_argument('--single-file', action='store_true', dest='single_file', default=False, help='render to single video')
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import subprocess
|
||||||
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 render_all, default_prefix
|
from ...render import render_all
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'genrate kdenlive porject and render'
|
help = 'genrate kdenlive porject and render'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
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('--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('--no-video', action='store_true', dest='no_video', default=False, help='don\'t render video')
|
parser.add_argument('--no-video', action='store_true', dest='no_video', default=False, help='don\'t render video')
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@ import subprocess
|
||||||
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 update_subtitles, default_prefix
|
from ...render import update_subtitles
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'genrate kdenlive porject and render'
|
help = 'genrate kdenlive porject and render'
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
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('--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)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
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
|
Hidden=false
|
||||||
NoDisplay=false
|
NoDisplay=false
|
||||||
X-GNOME-Autostart-enabled=true
|
X-GNOME-Autostart-enabled=true
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
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
|
Hidden=false
|
||||||
NoDisplay=false
|
NoDisplay=false
|
||||||
X-GNOME-Autostart-enabled=true
|
X-GNOME-Autostart-enabled=true
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ Wants=network-online.target
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
KillSignal=SIGINT
|
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]
|
[Install]
|
||||||
WantedBy=graphical-session.target
|
WantedBy=graphical-session.target
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ After=gnome-session.target network-online.target
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
KillSignal=SIGINT
|
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]
|
[Install]
|
||||||
WantedBy=graphical-session.target
|
WantedBy=graphical-session.target
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import mpv
|
||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger('p_for_power')
|
logger = logging.getLogger('t_for_time')
|
||||||
|
|
||||||
SYNC_TOLERANCE = 0.05
|
SYNC_TOLERANCE = 0.05
|
||||||
SYNC_GRACE_TIME = 5
|
SYNC_GRACE_TIME = 5
|
||||||
|
|
@ -74,7 +74,7 @@ class Sync(Thread):
|
||||||
input_vo_keyboard=True,
|
input_vo_keyboard=True,
|
||||||
)
|
)
|
||||||
self.sax.loop_file = 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:
|
else:
|
||||||
self.sax = None
|
self.sax = None
|
||||||
|
|
||||||
|
|
@ -408,11 +408,11 @@ class Sync(Thread):
|
||||||
|
|
||||||
|
|
||||||
def main():
|
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('--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('--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)
|
||||||
|
|
|
||||||
8
player/saxophone-loop.desktop
Normal file
8
player/saxophone-loop.desktop
Normal file
|
|
@ -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=
|
||||||
11
player/saxophone.service
Normal file
11
player/saxophone.service
Normal file
|
|
@ -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
|
||||||
350
render.py
350
render.py
|
|
@ -16,7 +16,6 @@ import lxml.etree
|
||||||
from .pi import random
|
from .pi import random
|
||||||
from .render_kdenlive import KDEnliveProject, _CACHE, melt_xml, get_melt
|
from .render_kdenlive import KDEnliveProject, _CACHE, melt_xml, get_melt
|
||||||
|
|
||||||
default_prefix = "/srv/p_for_power"
|
|
||||||
|
|
||||||
def random_int(seq, length):
|
def random_int(seq, length):
|
||||||
n = n_ = length - 1
|
n = n_ = length - 1
|
||||||
|
|
@ -62,7 +61,7 @@ def write_if_new(path, data, mode=''):
|
||||||
old = ""
|
old = ""
|
||||||
is_new = data != old
|
is_new = data != old
|
||||||
if path.endswith(".kdenlive"):
|
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:
|
if is_new:
|
||||||
with open(path, write_mode) as fd:
|
with open(path, write_mode) as fd:
|
||||||
fd.write(data)
|
fd.write(data)
|
||||||
|
|
@ -372,33 +371,17 @@ def get_offset_duration(prefix):
|
||||||
duration += get_scene_duration(scene)
|
duration += get_scene_duration(scene)
|
||||||
return duration
|
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):
|
def render(root, scene, prefix='', options=None):
|
||||||
if options is None:
|
if options is None: options = {}
|
||||||
options = {}
|
|
||||||
fps = 24
|
fps = 24
|
||||||
files = []
|
files = []
|
||||||
scene_duration = int(get_scene_duration(scene) * fps)
|
scene_duration = int(get_scene_duration(scene) * fps)
|
||||||
for timeline, data in scene.items():
|
for timeline, data in scene.items():
|
||||||
if timeline == "subtitles":
|
if timeline == "subtitles":
|
||||||
folder = Path(root) / prefix
|
path = os.path.join(root, prefix + "front.srt")
|
||||||
write_subtitles(data, folder, options)
|
data = fix_overlaps(data)
|
||||||
|
srt = ox.srt.encode(data)
|
||||||
|
write_if_new(path, srt, 'b')
|
||||||
continue
|
continue
|
||||||
#print(timeline)
|
#print(timeline)
|
||||||
project = KDEnliveProject(root)
|
project = KDEnliveProject(root)
|
||||||
|
|
@ -486,32 +469,15 @@ def get_fragments(clips, voice_over, prefix):
|
||||||
fragment['clips'] = []
|
fragment['clips'] = []
|
||||||
for clip in clips:
|
for clip in clips:
|
||||||
#if set(clip['tags']) & set(fragment['tags']) and not set(clip['tags']) & set(fragment['anti-tags']):
|
#if set(clip['tags']) & set(fragment['tags']) and not set(clip['tags']) & set(fragment['anti-tags']):
|
||||||
key = 'original'
|
if clip['original'] in originals:
|
||||||
original = clip['original']
|
|
||||||
if 'original_censored' in clip:
|
|
||||||
original = clip['original_censored']
|
|
||||||
if original in originals:
|
|
||||||
fragment['clips'].append(clip)
|
fragment['clips'].append(clip)
|
||||||
fragment["voice_over"] = voice_over.get(str(fragment["id"]), {})
|
fragment["voice_over"] = voice_over.get(str(fragment["id"]), {})
|
||||||
fragments.append(fragment)
|
fragments.append(fragment)
|
||||||
fragments.sort(key=lambda f: ox.sort_string(f['name']))
|
fragments.sort(key=lambda f: ox.sort_string(f['name']))
|
||||||
return fragments
|
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):
|
def render_all(options):
|
||||||
options = load_defaults(options)
|
|
||||||
prefix = options['prefix']
|
prefix = options['prefix']
|
||||||
duration = int(options['duration'])
|
duration = int(options['duration'])
|
||||||
base = int(options['offset'])
|
base = int(options['offset'])
|
||||||
|
|
@ -801,7 +767,7 @@ def render_all(options):
|
||||||
fn = base_prefix / fn
|
fn = base_prefix / fn
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
os.unlink(fn)
|
os.unlink(fn)
|
||||||
join_subtitles(base_prefix, options)
|
join_subtitles(base_prefix)
|
||||||
|
|
||||||
print("Duration - Target: %s Actual: %s" % (target_position, position))
|
print("Duration - Target: %s Actual: %s" % (target_position, position))
|
||||||
print(json.dumps(dict(stats), sort_keys=True, indent=2))
|
print(json.dumps(dict(stats), sort_keys=True, indent=2))
|
||||||
|
|
@ -824,39 +790,11 @@ def add_translations(sub, lang):
|
||||||
value += '\n' + tvalue
|
value += '\n' + tvalue
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def add_translations_dict(sub, langs):
|
def get_srt(sub, offset=0, lang=None):
|
||||||
values = {}
|
|
||||||
value = sub.value.replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\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('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
|
||||||
if tsub.languages:
|
|
||||||
tvalue = ox.strip_tags(tvalue)
|
|
||||||
values[slang] = tvalue
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
def get_srt(sub, offset, lang, tlang):
|
|
||||||
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').strip()
|
sdata['value'] = sdata['value'].replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
||||||
if tlang:
|
if lang:
|
||||||
sdata['value'] = add_translations(sub, tlang)
|
sdata['value'] = add_translations(sub, lang)
|
||||||
langs = [lang]
|
|
||||||
if tlang:
|
|
||||||
langs += tlang
|
|
||||||
sdata['values'] = add_translations_dict(sub, langs)
|
|
||||||
if offset:
|
if offset:
|
||||||
sdata["in"] += offset
|
sdata["in"] += offset
|
||||||
sdata["out"] += offset
|
sdata["out"] += offset
|
||||||
|
|
@ -873,55 +811,21 @@ def fix_overlaps(data):
|
||||||
previous = sub
|
previous = sub
|
||||||
return data
|
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):
|
def update_subtitles(options):
|
||||||
import item.models
|
import item.models
|
||||||
|
|
||||||
options = load_defaults(options)
|
|
||||||
prefix = Path(options['prefix'])
|
prefix = Path(options['prefix'])
|
||||||
base = int(options['offset'])
|
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")
|
_cache = os.path.join(prefix, "cache.json")
|
||||||
if os.path.exists(_cache):
|
if os.path.exists(_cache):
|
||||||
|
|
@ -936,59 +840,25 @@ def update_subtitles(options):
|
||||||
continue
|
continue
|
||||||
with open(scene_json) as fd:
|
with open(scene_json) as fd:
|
||||||
scene = json.load(fd)
|
scene = json.load(fd)
|
||||||
subs = scene_subtitles(scene, options)
|
offset = 0
|
||||||
write_subtitles(subs, folder, options)
|
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=[]):
|
def update_m3u(render_prefix, exclude=[]):
|
||||||
files = ox.sorted_strings(glob(render_prefix + "*/*/back.mp4"))
|
files = ox.sorted_strings(glob(render_prefix + "*/*/back.mp4"))
|
||||||
|
|
@ -1061,8 +931,7 @@ def render_infinity(options):
|
||||||
shutil.move(state_f + "~", state_f)
|
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)))
|
subtitles = list(sorted(glob('%s/*/front.srt' % base_prefix)))
|
||||||
data = []
|
data = []
|
||||||
position = 0
|
position = 0
|
||||||
|
|
@ -1072,142 +941,3 @@ def join_subtitles(base_prefix, options):
|
||||||
position += get_scene_duration(scene)
|
position += get_scene_duration(scene)
|
||||||
with open(base_prefix / 'front.srt', 'wb') as fd:
|
with open(base_prefix / 'front.srt', 'wb') as fd:
|
||||||
fd.write(ox.srt.encode(data))
|
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)
|
|
||||||
|
|
|
||||||
109
sax.py
Normal file
109
sax.py
Normal file
|
|
@ -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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue