add voice over, output 6 channels, add 5.1 mix

This commit is contained in:
j 2023-10-16 23:26:09 +01:00
commit 9c778bb7de
3 changed files with 263 additions and 37 deletions

116
render.py
View file

@ -4,10 +4,11 @@ import os
import subprocess
import sys
import time
from pathlib import Path
import ox
from .pi import random
from .render_kdenlive import KDEnliveProject
from .render_kdenlive import KDEnliveProject, _CACHE
def random_choice(seq, items, pop=False):
@ -34,7 +35,7 @@ def chance(seq, chance):
return (seq() / 10) >= chance
def compose(clips, target=150, base=1024):
def compose(clips, target=150, base=1024, voice_over=None):
length = 0
scene = {
'front': {
@ -54,6 +55,30 @@ def compose(clips, target=150, base=1024):
}
all_clips = clips.copy()
seq = random(base)
voice_overs = []
if voice_over:
vo_keys = list(voice_over)
if chance(seq, 0.5):
voice_overs.append(voice_over[vo_keys[chance(seq, len(vo_keys))]])
elif len(vo_keys) >= 2:
vo1 = vo_keys.pop(chance(seq, len(vo_keys)))
vo2 = vo_keys.pop(chance(seq, len(vo_keys)))
voice_overs.append(voice_over[vo1])
if voice_over[vo1]["duration"] + voice_over[vo2]["duration"] < target:
voice_overs.append(voice_over[vo2])
vo_min = sum([vo['duration'] for vo in voice_overs])
if vo_min > target:
target = vo_min
if vo_min < target:
offset = (target - vo_min) / 2
scene['audio']['A3'].append({
'blank': True,
'duration': offset
})
for vo in voice_overs:
scene['audio']['A3'].append(vo)
while target - length > 0 and clips:
clip = random_choice(seq, clips, True)
if not clips:
@ -119,6 +144,7 @@ def compose(clips, target=150, base=1024):
'duration': clip['duration'],
'src': fg,
})
return scene
def get_scene_duration(scene):
@ -145,11 +171,29 @@ def render(root, scene, prefix=''):
with open(path, 'w') as fd:
fd.write(project.to_xml())
files.append(path)
if timeline == "audio":
duration = project.get_duration()
for track, clips in data.items():
project = KDEnliveProject(root)
for clip in clips:
project.append_clip(track, clip)
track_duration = project.get_duration()
delta = duration - track_duration
if delta > 0:
project.append_clip(track, {'blank': True, "duration": delta/24})
path = os.path.join(root, prefix + "%s-%s.kdenlive" % (timeline, track))
with open(path, 'w') as fd:
fd.write(project.to_xml())
files.append(path)
return files
def get_fragments(clips):
def get_fragments(clips, voice_over):
import itemlist.models
import item.models
from collections import defaultdict
fragments = []
for l in itemlist.models.List.objects.filter(status='featured').order_by('name'):
if l.name.split(' ')[0].isdigit():
fragment = {
@ -157,10 +201,12 @@ def get_fragments(clips):
'tags': [t['value'] for t in l.query['conditions'][1]['conditions']],
'description': l.description
}
fragment["id"] = int(fragment['name'].split(' ')[0])
fragment['clips'] = []
for clip in clips:
if set(clip['tags']) & set(fragment['tags']):
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
@ -171,17 +217,25 @@ def render_all(options):
duration = int(options['duration'])
base = int(options['offset'])
_cache = os.path.join(prefix, "cache.json")
if os.path.exists(_cache):
with open(_cache) as fd:
_CACHE.update(json.load(fd))
with open(os.path.join(prefix, "clips.json")) as fd:
clips = json.load(fd)
fragments = get_fragments(clips)
with open(os.path.join(prefix, "voice_over.json")) as fd:
voice_over = json.load(fd)
fragments = get_fragments(clips, voice_over)
with open(os.path.join(prefix, "fragments.json"), "w") as fd:
json.dump(fragments, fd, indent=2, ensure_ascii=False)
position = target_position = 0
target = fragment_target = duration / len(fragments)
base_prefix = os.path.join(prefix, 'render', str(base))
for fragment in fragments:
n = int(fragment['name'].split(' ')[0])
fragment_id = int(fragment['name'].split(' ')[0])
name = fragment['name'].replace(' ', '_')
if n < 10:
if fragment_id < 10:
name = '0' + name
if not fragment['clips']:
print("skipping empty fragment", name)
@ -189,7 +243,7 @@ def render_all(options):
fragment_prefix = os.path.join(base_prefix, name)
os.makedirs(fragment_prefix, exist_ok=True)
scene = compose(fragment['clips'], target=target, base=base)
scene = compose(fragment['clips'], target=target, base=base, voice_over=fragment['voice_over'])
scene_duration = get_scene_duration(scene)
print("%s %s -> %s (%s)" % (name, target, scene_duration, fragment_target))
position += scene_duration
@ -207,22 +261,58 @@ def render_all(options):
if not options['no_video']:
for timeline in timelines:
print(timeline)
ext = '.mp4'
if '-audio.kdenlive' in timeline:
if '/audio' in timeline:
ext = '.wav'
cmd = [
'xvfb-run', '-a',
'melt', timeline,
'-consumer', 'avformat:%s' % timeline.replace('.kdenlive', ext)
'-consumer', 'avformat:%s' % timeline.replace('.kdenlive', ext),
'-quiet'
]
subprocess.call(cmd)
if ext == '.wav':
if ext == '.wav' and timeline.endswith('audio.kdenlive'):
cmd = [
'ffmpeg', '-i',
'ffmpeg', '-y',
'-nostats', '-loglevel', 'error',
'-i',
timeline.replace('.kdenlive', ext),
timeline.replace('.kdenlive', '.mp4')
]
subprocess.call(cmd)
os.unlink(timeline.replace('.kdenlive', ext))
print("Duration - Target: %s Actual: %s" % (target_position, position))
fragment_prefix = Path(fragment_prefix)
cmds = []
for src, out1, out2 in (
('audio-A1.wav', 'fl.wav', 'fr.wav'),
('audio-A2.wav', 'fc.wav', 'lfe.wav'),
('audio-A3.wav', 'bl.wav', 'br.wav'),
):
cmds.append([
'ffmpeg', '-y',
'-nostats', '-loglevel', 'error',
'-i', fragment_prefix / src,
'-filter_complex',
"[0:0]pan=1|c0=c0[left]; [0:0]pan=1|c0=c1[right]",
"-map", "[left]", fragment_prefix / out1,
"-map", "[right]", fragment_prefix / out2,
])
cmds.append([
'ffmpeg', '-y',
'-nostats', '-loglevel', 'error',
'-i', fragment_prefix / "fl.wav",
'-i', fragment_prefix / "fr.wav",
'-i', fragment_prefix / "fc.wav",
'-i', fragment_prefix / "lfe.wav",
'-i', fragment_prefix / "bl.wav",
'-i', fragment_prefix / "br.wav",
'-filter_complex', "[0:a][1:a][2:a][3:a][4:a][5:a]amerge=inputs=6[a]",
"-map", "[a]", "-c:a", "aac", fragment_prefix / "audio-5.1.mp4"
])
for cmd in cmds:
subprocess.call(cmd)
print("Duration - Target: %s Actual: %s" % (target_position, position))
with open(_cache, "w") as fd:
json.dump(_CACHE, fd)