diff --git a/management/commands/generate_clips.py b/management/commands/generate_clips.py index 4922d53..3d0091e 100644 --- a/management/commands/generate_clips.py +++ b/management/commands/generate_clips.py @@ -19,10 +19,12 @@ class Command(BaseCommand): clips = [] for i in item.models.Item.objects.filter(data__type__contains="Original"): qs = item.models.Item.objects.filter(data__title=i.data['title']).exclude(id=i.id) - if qs.count() == 2: + if qs.count() >= 2: 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", e) source = e.files.all()[0].data.path ext = os.path.splitext(source)[1] type_ = e.data['type'][0].lower() @@ -35,7 +37,10 @@ class Command(BaseCommand): durations.append(e.files.all()[0].duration) clip["duration"] = min(durations) clip['tags'] = i.data.get('tags', []) - clips.append(clip) + if "original" in clip and "foreground" in clip and "background" 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) diff --git a/management/commands/render.py b/management/commands/render.py index b43dbc9..24ba964 100644 --- a/management/commands/render.py +++ b/management/commands/render.py @@ -1,5 +1,6 @@ import json import os +import subprocess from django.core.management.base import BaseCommand from django.conf import settings @@ -14,6 +15,7 @@ class Command(BaseCommand): 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="150", help='target duration 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') def handle(self, **options): prefix = options['prefix'] @@ -23,7 +25,15 @@ class Command(BaseCommand): with open(os.path.join(prefix, "clips.json")) as fd: clips = json.load(fd) scene = compose(clips, target=target, base=base) - render(prefix, scene, 'scene-%s-' % base) + timelines = render(prefix, scene, 'scene-%s-' % base) with open(os.path.join(prefix, 'scene-%s.json' % base), 'w') as fd: json.dump(scene, fd, indent=2, ensure_ascii=False) + if not options['no_video']: + for timeline in timelines: + cmd = [ + 'xvfb-run', '-a', + 'melt', timeline, + '-consumer', 'avformat:%s' % timeline.replace('.kdenlive', '.mp4') + ] + subprocess.call(cmd) diff --git a/render.py b/render.py index cdb70eb..0a879be 100644 --- a/render.py +++ b/render.py @@ -14,7 +14,9 @@ def random_choice(seq, items, pop=False): n = n_ = len(items) - 1 #print('len', n) if n == 0: - return items[0] + if pop: + return items.pop(n) + return items[n] r = seq() base = 10 while n > 10: @@ -50,9 +52,12 @@ def compose(clips, target=150, base=1024): 'A4': [], } } + all_clips = clips.copy() seq = random(base) while target - length > 10 and clips: clip = random_choice(seq, clips, True) + if not clips: + clips = [c for c in all_clips if c != clip] if length + clip['duration'] > target: break length += clip['duration'] @@ -112,6 +117,7 @@ def compose(clips, target=150, base=1024): def render(root, scene, prefix=''): fps = 24 + files = [] for timeline, data in scene.items(): print(timeline) project = KDEnliveProject(root) @@ -121,6 +127,8 @@ def render(root, scene, prefix=''): print(track) for clip in clips: project.append_clip(track, clip) - - with open(os.path.join(root, prefix + "%s.kdenlive" % timeline), 'w') as fd: + path = os.path.join(root, prefix + "%s.kdenlive" % timeline) + with open(path, 'w') as fd: fd.write(project.to_xml()) + files.append(path) + return files diff --git a/render_kdenlive.py b/render_kdenlive.py index 6daf309..58e68a6 100644 --- a/render_kdenlive.py +++ b/render_kdenlive.py @@ -5,6 +5,7 @@ import lxml.etree import uuid import os +_CACHE = {} _IDS = defaultdict(int) def get_propery(element, name): @@ -12,6 +13,7 @@ def get_propery(element, name): + class KDEnliveProject: def to_xml(self): @@ -316,7 +318,10 @@ class KDEnliveProject: return prefix + self.get_counter(prefix) def get_chain(self, file, kdenlive_id=None): - out = subprocess.check_output(['melt', file, '-consumer', 'xml']) + if file in _CACHE: + out = _CACHE[file] + else: + out = _CACHE[file] = subprocess.check_output(['melt', file, '-consumer', 'xml']) chain = lxml.etree.fromstring(out).xpath('producer')[0] chain.tag = 'chain' chain.attrib['id'] = self.get_id('chain')