split render, prepare for single video
This commit is contained in:
parent
48ab9cf951
commit
6b40002528
3 changed files with 199 additions and 327 deletions
337
render.py
337
render.py
|
|
@ -11,64 +11,14 @@ import time
|
|||
from pathlib import Path
|
||||
|
||||
import ox
|
||||
import lxml.etree
|
||||
|
||||
from .pi import random
|
||||
from .render_kdenlive import KDEnliveProject, _CACHE, melt_xml, get_melt
|
||||
from .render_kdenlive import KDEnliveProject, _CACHE, get_melt
|
||||
from .utils import resolve_roman, write_if_new, format_duration
|
||||
from .render_utils import *
|
||||
|
||||
default_prefix = "/srv/p_for_power"
|
||||
|
||||
def random_int(seq, length):
|
||||
n = n_ = length - 1
|
||||
#print('len', n)
|
||||
if n == 0:
|
||||
return n
|
||||
r = seq() / 9 * 10
|
||||
base = 10
|
||||
while n > 10:
|
||||
n /= 10
|
||||
r += seq() / 9 * 10
|
||||
base += 10
|
||||
r = int(round(n_ * r / base))
|
||||
return r
|
||||
|
||||
def random_choice(seq, items, pop=False):
|
||||
n = random_int(seq, len(items))
|
||||
if pop:
|
||||
return items.pop(n)
|
||||
return items[n]
|
||||
|
||||
def chance(seq, chance):
|
||||
return (seq() / 10) < chance
|
||||
|
||||
def get_clip_by_seqid(clips, seqid):
|
||||
selected = None
|
||||
for i, clip in enumerate(clips):
|
||||
if clip['seqid'] == seqid:
|
||||
selected = i
|
||||
break
|
||||
if selected is not None:
|
||||
return clips.pop(i)
|
||||
return None
|
||||
|
||||
|
||||
def write_if_new(path, data, mode=''):
|
||||
read_mode = 'r' + mode
|
||||
write_mode = 'w' + mode
|
||||
if os.path.exists(path):
|
||||
with open(path, read_mode) as fd:
|
||||
old = fd.read()
|
||||
else:
|
||||
old = ""
|
||||
is_new = data != old
|
||||
if path.endswith(".kdenlive"):
|
||||
is_new = re.sub(r'\{.{36}\}', '', data) != re.sub(r'\{.{36}\}', '', old)
|
||||
if is_new:
|
||||
with open(path, write_mode) as fd:
|
||||
fd.write(data)
|
||||
|
||||
def format_duration(duration, fps):
|
||||
return float('%0.5f' % (round(duration * fps) / fps))
|
||||
|
||||
def compose(clips, target=150, base=1024, voice_over=None, options=None):
|
||||
if options is None:
|
||||
|
|
@ -80,13 +30,6 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
|
|||
'V1': [],
|
||||
'V2': [],
|
||||
},
|
||||
'back': {
|
||||
'V1': [],
|
||||
'V2': [],
|
||||
},
|
||||
'audio-back': {
|
||||
'A1': [],
|
||||
},
|
||||
'audio-center': {
|
||||
'A1': [],
|
||||
},
|
||||
|
|
@ -251,9 +194,7 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
|
|||
#if chance(seq, 0.5):
|
||||
if chance(seq, 0.8):
|
||||
transparency_front = transparency
|
||||
transparency_back = 0
|
||||
else:
|
||||
transparency_back = random_choice(seq, [0.25, 0.5, 0.75, 1])
|
||||
transparency_front = 0
|
||||
transparency_original = seq() / 9
|
||||
transparency_original = 1
|
||||
|
|
@ -265,13 +206,6 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
|
|||
'transparency': transparency_front
|
||||
}
|
||||
})
|
||||
scene['back']['V2'].append({
|
||||
'duration': clip['duration'],
|
||||
'src': clip['background'],
|
||||
"filter": {
|
||||
'transparency': transparency_back
|
||||
}
|
||||
})
|
||||
else:
|
||||
scene['front']['V1'].append({
|
||||
'duration': clip['duration'],
|
||||
|
|
@ -280,34 +214,7 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
|
|||
'transparency': 0,
|
||||
}
|
||||
})
|
||||
scene['back']['V2'].append({
|
||||
'duration': clip['duration'],
|
||||
'src': clip['original'],
|
||||
"filter": {
|
||||
'transparency': 0,
|
||||
}
|
||||
})
|
||||
|
||||
scene['back']['V1'].append({
|
||||
'duration': clip['duration'],
|
||||
'src': clip['original'],
|
||||
"filter": {
|
||||
'transparency': transparency_original,
|
||||
}
|
||||
})
|
||||
# 50 % chance to blur original from 0 to 30
|
||||
if chance(seq, 0.5):
|
||||
blur = seq() * 3
|
||||
if blur:
|
||||
scene['back']['V1'][-1]['filter']['blur'] = blur
|
||||
volume_back = '-8.2'
|
||||
if options.get('stereo_downmix'):
|
||||
volume_back = '-7.2'
|
||||
scene['audio-back']['A1'].append({
|
||||
'duration': clip['duration'],
|
||||
'src': clip['original'],
|
||||
'filter': {'volume': volume_back},
|
||||
})
|
||||
# TBD: Foley
|
||||
cf_volume = '-2.5'
|
||||
scene['audio-front']['A2'].append({
|
||||
|
|
@ -343,35 +250,6 @@ def compose(clips, target=150, base=1024, voice_over=None, options=None):
|
|||
print(scene['audio-center']['A1'][-1])
|
||||
return scene, used
|
||||
|
||||
def get_track_duration(scene, k, n):
|
||||
duration = 0
|
||||
for key, value in scene.items():
|
||||
if key == k:
|
||||
for name, clips in value.items():
|
||||
if name == n:
|
||||
for clip in clips:
|
||||
duration += int(clip['duration'] * 24)
|
||||
return duration / 24
|
||||
|
||||
def get_scene_duration(scene):
|
||||
if isinstance(scene, str):
|
||||
with open(scene) as fd:
|
||||
scene = json.load(fd)
|
||||
duration = 0
|
||||
for key, value in scene.items():
|
||||
for name, clips in value.items():
|
||||
for clip in clips:
|
||||
duration += int(clip['duration'] * 24)
|
||||
return duration / 24
|
||||
|
||||
def get_offset_duration(prefix):
|
||||
duration = 0
|
||||
for root, folders, files in os.walk(prefix):
|
||||
for f in files:
|
||||
if f == 'scene.json':
|
||||
duration += get_scene_duration(scene)
|
||||
return duration
|
||||
|
||||
def write_subtitles(data, folder, options):
|
||||
data = fix_overlaps(data)
|
||||
path = folder / "front.srt"
|
||||
|
|
@ -440,12 +318,6 @@ def render(root, scene, prefix='', options=None):
|
|||
files.append(path)
|
||||
return files
|
||||
|
||||
def get_project_duration(file):
|
||||
out = melt_xml(file)
|
||||
chain = lxml.etree.fromstring(out).xpath('producer')[0]
|
||||
duration = int(chain.attrib['out']) + 1
|
||||
return duration
|
||||
|
||||
def get_fragments(clips, voice_over, prefix):
|
||||
import itemlist.models
|
||||
import item.models
|
||||
|
|
@ -488,8 +360,6 @@ def get_fragments(clips, voice_over, prefix):
|
|||
#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:
|
||||
fragment['clips'].append(clip)
|
||||
fragment["voice_over"] = voice_over.get(str(fragment["id"]), {})
|
||||
|
|
@ -497,19 +367,6 @@ def get_fragments(clips, voice_over, prefix):
|
|||
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']
|
||||
|
|
@ -589,7 +446,6 @@ def render_all(options):
|
|||
if ext == '.wav':
|
||||
cmd += ['vn=1']
|
||||
else:
|
||||
#if not timeline.endswith("back.kdenlive"):
|
||||
cmd += ['an=1']
|
||||
cmd += ['vcodec=libx264', 'x264opts=keyint=1', 'crf=15']
|
||||
subprocess.call(cmd)
|
||||
|
|
@ -633,7 +489,6 @@ def render_all(options):
|
|||
"-map", "[a]", "-c:a", "aac", fragment_prefix / "audio-5.1.mp4"
|
||||
])
|
||||
audio_front = "audio-5.1.mp4"
|
||||
audio_back = "audio-back.wav"
|
||||
copy = '-c'
|
||||
if options["stereo_downmix"]:
|
||||
cmds.append([
|
||||
|
|
@ -642,13 +497,11 @@ def render_all(options):
|
|||
"-i", fragment_prefix / "audio-front.wav",
|
||||
"-i", fragment_prefix / "audio-center.wav",
|
||||
"-i", fragment_prefix / "audio-rear.wav",
|
||||
"-i", fragment_prefix / audio_back,
|
||||
"-filter_complex",
|
||||
"amix=inputs=4:duration=longest:dropout_transition=0",
|
||||
'-ac', '2', fragment_prefix / "audio-stereo.wav"
|
||||
])
|
||||
audio_front = "audio-stereo.wav"
|
||||
audio_back = "audio-stereo.wav"
|
||||
copy = '-c:v'
|
||||
|
||||
cmds.append([
|
||||
|
|
@ -660,22 +513,12 @@ def render_all(options):
|
|||
"-movflags", "+faststart",
|
||||
fragment_prefix / "front-mixed.mp4",
|
||||
])
|
||||
cmds.append([
|
||||
"ffmpeg", "-y",
|
||||
"-nostats", "-loglevel", "error",
|
||||
"-i", fragment_prefix / "back.mp4",
|
||||
"-i", fragment_prefix / audio_back,
|
||||
"-c:v", "copy",
|
||||
"-movflags", "+faststart",
|
||||
fragment_prefix / "back-audio.mp4",
|
||||
])
|
||||
for cmd in cmds:
|
||||
if options["debug"]:
|
||||
print(" ".join([str(x) for x in cmd]))
|
||||
subprocess.call(cmd)
|
||||
|
||||
for a, b in (
|
||||
("back-audio.mp4", "back.mp4"),
|
||||
("front-mixed.mp4", "front.mp4"),
|
||||
):
|
||||
duration_a = ox.avinfo(str(fragment_prefix / a))['duration']
|
||||
|
|
@ -684,16 +527,14 @@ def render_all(options):
|
|||
print('!!', duration_a, fragment_prefix / a)
|
||||
print('!!', duration_b, fragment_prefix / b)
|
||||
sys.exit(-1)
|
||||
shutil.move(fragment_prefix / "back-audio.mp4", fragment_prefix / "back.mp4")
|
||||
shutil.move(fragment_prefix / "front-mixed.mp4", fragment_prefix / "front.mp4")
|
||||
if options["keep_audio"]:
|
||||
shutil.move(fragment_prefix / "audio-center.wav", fragment_prefix / "vocals.wav")
|
||||
shutil.move(fragment_prefix / "audio-front.wav", fragment_prefix / "foley.wav")
|
||||
shutil.move(fragment_prefix / "audio-back.wav", fragment_prefix / "original.wav")
|
||||
for fn in (
|
||||
"audio-5.1.mp4",
|
||||
"audio-center.wav", "audio-rear.wav",
|
||||
"audio-front.wav", "audio-back.wav", "back-audio.mp4",
|
||||
"audio-front.wav",
|
||||
"fl.wav", "fr.wav", "fc.wav", "lfe.wav", "bl.wav", "br.wav",
|
||||
"audio-stereo.wav",
|
||||
):
|
||||
|
|
@ -706,8 +547,6 @@ def render_all(options):
|
|||
base_prefix = Path(base_prefix)
|
||||
for timeline in (
|
||||
"front",
|
||||
"back",
|
||||
"audio-back",
|
||||
"audio-center",
|
||||
"audio-front",
|
||||
"audio-rear",
|
||||
|
|
@ -762,40 +601,19 @@ def render_all(options):
|
|||
"-movflags", "+faststart",
|
||||
base_prefix / "front-mixed.mp4",
|
||||
])
|
||||
cmds.append([
|
||||
"ffmpeg", "-y",
|
||||
"-nostats", "-loglevel", "error",
|
||||
"-i", base_prefix / "back.mp4",
|
||||
"-i", base_prefix / "audio-back.wav",
|
||||
"-c:v", "copy",
|
||||
"-movflags", "+faststart",
|
||||
base_prefix / "back-audio.mp4",
|
||||
])
|
||||
for cmd in cmds:
|
||||
if options["debug"]:
|
||||
print(" ".join([str(x) for x in cmd]))
|
||||
subprocess.call(cmd)
|
||||
|
||||
for a, b in (
|
||||
("back-audio.mp4", "back.mp4"),
|
||||
("front-mixed.mp4", "back.mp4"),
|
||||
):
|
||||
duration_a = ox.avinfo(str(base_prefix / a))['duration']
|
||||
duration_b = ox.avinfo(str(base_prefix / b))['duration']
|
||||
if duration_a != duration_b:
|
||||
print('!!', duration_a, base_prefix / a)
|
||||
print('!!', duration_b, base_prefix / b)
|
||||
sys.exit(-1)
|
||||
shutil.move(base_prefix / "back-audio.mp4", base_prefix / "back.mp4")
|
||||
shutil.move(base_prefix / "front-mixed.mp4", base_prefix / "front.mp4")
|
||||
if options["keep_audio"]:
|
||||
shutil.move(base_prefix / "audio-center.wav", base_prefix / "vocals.wav")
|
||||
shutil.move(base_prefix / "audio-front.wav", base_prefix / "foley.wav")
|
||||
shutil.move(base_prefix / "audio-back.wav", base_prefix / "original.wav")
|
||||
for fn in (
|
||||
"audio-5.1.mp4",
|
||||
"audio-center.wav", "audio-rear.wav",
|
||||
"audio-front.wav", "audio-back.wav", "back-audio.mp4",
|
||||
"audio-front.wav",
|
||||
"fl.wav", "fr.wav", "fc.wav", "lfe.wav", "bl.wav", "br.wav",
|
||||
):
|
||||
fn = base_prefix / fn
|
||||
|
|
@ -847,7 +665,6 @@ def add_translations_dict(sub, langs):
|
|||
values[slang] = tvalue
|
||||
return values
|
||||
|
||||
|
||||
def get_srt(sub, offset, lang, tlang):
|
||||
sdata = sub.json(keys=['in', 'out', 'value'])
|
||||
sdata['value'] = sdata['value'].replace('<br/>', '<br>').replace('<br>\n', '\n').replace('<br>', '\n').strip()
|
||||
|
|
@ -862,23 +679,6 @@ def get_srt(sub, offset, lang, tlang):
|
|||
sdata["out"] += offset
|
||||
return sdata
|
||||
|
||||
def fix_overlaps(data):
|
||||
previous = None
|
||||
for sub in data:
|
||||
if previous is None:
|
||||
previous = sub
|
||||
else:
|
||||
if sub['in'] < previous['out']:
|
||||
previous['out'] = sub['in'] - 0.001
|
||||
previous = sub
|
||||
return data
|
||||
|
||||
def shift_clips(data, offset):
|
||||
for clip in data:
|
||||
clip['in'] += offset
|
||||
clip['out'] += offset
|
||||
return data
|
||||
|
||||
def scene_subtitles(scene, options):
|
||||
import item.models
|
||||
offset = 0
|
||||
|
|
@ -940,77 +740,18 @@ def update_subtitles(options):
|
|||
subs = scene_subtitles(scene, options)
|
||||
write_subtitles(subs, folder, options)
|
||||
|
||||
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"))
|
||||
files = ox.sorted_strings(glob(render_prefix + "*/*/front.mp4"))
|
||||
for ex in exclude:
|
||||
files = [f for f in files if not f.startswith(ex + "/")]
|
||||
back_m3u = "\n".join(files)
|
||||
back_m3u = back_m3u.replace(render_prefix, "")
|
||||
front_m3u = back_m3u.replace("back.mp4", "front.mp4")
|
||||
front_m3u = "\n".join(files)
|
||||
front_m3u = front_m3u.replace(render_prefix, "")
|
||||
|
||||
back_m3u_f = render_prefix + "back.m3u"
|
||||
front_m3u_f = render_prefix + "front.m3u"
|
||||
|
||||
with open(back_m3u_f + "_", "w") as fd:
|
||||
fd.write(back_m3u)
|
||||
with open(front_m3u_f + "_", "w") as fd:
|
||||
fd.write(front_m3u)
|
||||
shutil.move(front_m3u_f + "_", front_m3u_f)
|
||||
cmd = ["scp", front_m3u_f, "front:" + front_m3u_f]
|
||||
subprocess.check_call(cmd)
|
||||
shutil.move(back_m3u_f + "_", back_m3u_f)
|
||||
|
||||
|
||||
def render_infinity(options):
|
||||
prefix = options['prefix']
|
||||
|
|
@ -1048,13 +789,7 @@ def render_infinity(options):
|
|||
folder = render_prefix + folder
|
||||
print("remove", folder)
|
||||
shutil.rmtree(folder)
|
||||
cmd = ["ssh", "front", "rm", "-rf", folder]
|
||||
#print(cmd)
|
||||
subprocess.check_call(cmd)
|
||||
render_all(state)
|
||||
path = "%s%s/" % (render_prefix, state["offset"])
|
||||
cmd = ['rsync', '-a', path, "front:" + path]
|
||||
subprocess.check_call(cmd)
|
||||
update_m3u(render_prefix)
|
||||
state["offset"] += 1
|
||||
with open(state_f + "~", "w") as fd:
|
||||
|
|
@ -1063,17 +798,6 @@ def render_infinity(options):
|
|||
|
||||
|
||||
def join_subtitles(base_prefix, options):
|
||||
'''
|
||||
subtitles = list(sorted(glob('%s/*/front.srt' % base_prefix)))
|
||||
data = []
|
||||
position = 0
|
||||
for srt in subtitles:
|
||||
scene = srt.replace('front.srt', 'scene.json')
|
||||
data += ox.srt.load(srt, offset=position)
|
||||
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
|
||||
|
|
@ -1085,17 +809,6 @@ def join_subtitles(base_prefix, options):
|
|||
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
|
||||
|
|
@ -1103,11 +816,6 @@ def generate_clips(options):
|
|||
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 = ""
|
||||
|
|
@ -1131,9 +839,6 @@ def generate_clips(options):
|
|||
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)
|
||||
|
|
@ -1141,11 +846,8 @@ def generate_clips(options):
|
|||
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)
|
||||
|
|
@ -1161,12 +863,8 @@ def generate_clips(options):
|
|||
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)
|
||||
|
||||
clips.append(clip)
|
||||
|
||||
with open(os.path.join(prefix, 'clips.json'), 'w') as fd:
|
||||
json.dump(clips, fd, indent=2, ensure_ascii=False)
|
||||
|
|
@ -1199,18 +897,3 @@ def generate_clips(options):
|
|||
}
|
||||
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)
|
||||
|
|
|
|||
159
render_utils.py
Normal file
159
render_utils.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
import re
|
||||
import os
|
||||
|
||||
import lxml.etree
|
||||
|
||||
from .render_kdenlive melt_xml, get_melt
|
||||
|
||||
|
||||
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 random_int(seq, length):
|
||||
n = n_ = length - 1
|
||||
#print('len', n)
|
||||
if n == 0:
|
||||
return n
|
||||
r = seq() / 9 * 10
|
||||
base = 10
|
||||
while n > 10:
|
||||
n /= 10
|
||||
r += seq() / 9 * 10
|
||||
base += 10
|
||||
r = int(round(n_ * r / base))
|
||||
return r
|
||||
|
||||
def random_choice(seq, items, pop=False):
|
||||
n = random_int(seq, len(items))
|
||||
if pop:
|
||||
return items.pop(n)
|
||||
return items[n]
|
||||
|
||||
def chance(seq, chance):
|
||||
return (seq() / 10) < chance
|
||||
|
||||
def get_clip_by_seqid(clips, seqid):
|
||||
selected = None
|
||||
for i, clip in enumerate(clips):
|
||||
if clip['seqid'] == seqid:
|
||||
selected = i
|
||||
break
|
||||
if selected is not None:
|
||||
return clips.pop(i)
|
||||
return None
|
||||
|
||||
|
||||
def get_scene_duration(scene):
|
||||
if isinstance(scene, str):
|
||||
with open(scene) as fd:
|
||||
scene = json.load(fd)
|
||||
duration = 0
|
||||
for key, value in scene.items():
|
||||
for name, clips in value.items():
|
||||
for clip in clips:
|
||||
duration += int(clip['duration'] * 24)
|
||||
return duration / 24
|
||||
|
||||
|
||||
def get_offset_duration(prefix):
|
||||
duration = 0
|
||||
for root, folders, files in os.walk(prefix):
|
||||
for f in files:
|
||||
if f == 'scene.json':
|
||||
duration += get_scene_duration(scene)
|
||||
return duration
|
||||
|
||||
|
||||
def get_track_duration(scene, k, n):
|
||||
duration = 0
|
||||
for key, value in scene.items():
|
||||
if key == k:
|
||||
for name, clips in value.items():
|
||||
if name == n:
|
||||
for clip in clips:
|
||||
duration += int(clip['duration'] * 24)
|
||||
return duration / 24
|
||||
|
||||
def get_project_duration(file):
|
||||
out = melt_xml(file)
|
||||
chain = lxml.etree.fromstring(out).xpath('producer')[0]
|
||||
duration = int(chain.attrib['out']) + 1
|
||||
return duration
|
||||
|
||||
def fix_overlaps(data):
|
||||
previous = None
|
||||
for sub in data:
|
||||
if previous is None:
|
||||
previous = sub
|
||||
else:
|
||||
if sub['in'] < previous['out']:
|
||||
previous['out'] = sub['in'] - 0.001
|
||||
previous = sub
|
||||
return data
|
||||
|
||||
def shift_clips(data, offset):
|
||||
for clip in data:
|
||||
clip['in'] += offset
|
||||
clip['out'] += offset
|
||||
return data
|
||||
|
||||
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
|
||||
|
||||
30
utils.py
30
utils.py
|
|
@ -1,4 +1,16 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
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 upgrade_originals():
|
||||
import item.models
|
||||
|
|
@ -30,3 +42,21 @@ def remove_deselected_files():
|
|||
if changed:
|
||||
i.save()
|
||||
|
||||
def write_if_new(path, data, mode=''):
|
||||
read_mode = 'r' + mode
|
||||
write_mode = 'w' + mode
|
||||
if os.path.exists(path):
|
||||
with open(path, read_mode) as fd:
|
||||
old = fd.read()
|
||||
else:
|
||||
old = ""
|
||||
is_new = data != old
|
||||
if path.endswith(".kdenlive"):
|
||||
is_new = re.sub(r'\{.{36}\}', '', data) != re.sub(r'\{.{36}\}', '', old)
|
||||
if is_new:
|
||||
with open(path, write_mode) as fd:
|
||||
fd.write(data)
|
||||
|
||||
def format_duration(duration, fps):
|
||||
return float('%0.5f' % (round(duration * fps) / fps))
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue