commit eeaf7d62cedac369895e6185fa3fa1092df4138d Author: j <0x006A@0x2620.org> Date: Thu May 2 13:19:00 2013 +0200 mlt generation diff --git a/README b/README new file mode 100644 index 0000000..4f8b8b8 --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +generate mtl timeline for clips from a pan.do/ra instance + +run generate.py to create a temorary json playlist. +and use timeline.py to convert it into a kdenlive project diff --git a/generate.py b/generate.py new file mode 100755 index 0000000..9270063 --- /dev/null +++ b/generate.py @@ -0,0 +1,106 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +import json +import ox +import os + + +api = ox.API('https://0xdb.org/api/') +config = json.load(open(os.path.expanduser('~/.ox/client.json'))) +api.signin(username=config['username'], password=config['password']) + +def get_files(item): + files = api.findFiles({ + "keys": [ + "selected", + #"path", + "part", + "language", + "type", + "duration", + "instances" + ], + "query": { + "conditions":[{ + "key": "id", + "value": item, + "operator": "==" + }] + }, + "range": [0, 100], + "sort": [{"key": "path", "operator": "+"}] + })['data']['items'] + files = filter(lambda f: f['type'] == 'video' and f['selected'], files) + for f in files: + f['path'] = f['instances'][0]['path'] + del f['instances'] + + return files + +def get_clips(terms): + clips = [] + for term in terms: + r = api.findClips({ + "keys":["position","annotations","id","in","out","videoRatio", "parts"], + "query":{"conditions":[{"operator":"=","key":"subtitles","value":term}], + "operator":"&"}, + "range":[0,1000], + "sort":[{"operator":"+","key":"position"}], + #"sort":[{"operator":"+","key":"year"}], + "itemsQuery":{ + "operator":"&", + "conditions":[{"operator":"=","key":"subtitles","value":term}] + } + }) + if not 'data' in r or not 'items' in r['data']: + print r + clips += r['data']['items'] + + cache = {} + + playlist = [] + clips.sort(key=lambda c: c['in']) + + for clip in clips: + id = clip['id'].split('/')[0] + part = 1 + if not id in cache: + cache[id] = get_files(id) + data = cache[id] + position = 0 + #FIXME: handle clips that span across parts + for part, f in enumerate(data): + if clip['in'] < position + f['duration']: + clip_in = clip['in'] - position + clip_out = clip['out'] - position + break + else: + position += f['duration'] + + path = f['path'] + print path, clip_in, clip_out + subtitles = '' + for a in clip['annotations']: + subtitles += a['value'] + subtitles = ox.strip_tags(subtitles) + playlist.append({ + "id": id, + "part": part, + "path": path, + "in": clip_in, + "out": clip_out, + "subtitles": subtitles + }) + return playlist + +if __name__ == "__main__": + import sys + if len(sys.argv) != 3: + print 'usage: %s ' % sys.argv[0] + sys.exit(1) + terms = [sys.argv[1]] + output = sys.argv[2] + playlist = get_clips(terms) + with open(output, 'w') as f: + json.dump(playlist, f, indent=2) diff --git a/timeline.py b/timeline.py new file mode 100755 index 0000000..3d2ceab --- /dev/null +++ b/timeline.py @@ -0,0 +1,252 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# vi:si:et:sw=4:sts=4:ts=4 +from __future__ import division, with_statement +import os +import sys +import json + +import ox + +MIN_DURATION = 2 + +profiles = { + 'pal': '' +} + +class Video: + position = 0 + playlist = [] + producers = {} + + template = ''' + + %(profile)s + + producer + 72870 + pause + black + 0 + colour + + + + + + + + + %(producers)s + + + %(playlist)s + + + + 1 + + + + + + + + 1 + 2 + transition + mix + 1 + 1 + 237 + + + 1 + 3 + transition + mix + 1 + 1 + 237 + + + 1 + 4 + transition + mix + 1 + 1 + 237 + + + 1 + 5 + transition + mix + 1 + 1 + 237 + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:8pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + + %(kdenlive_producers)s + + + + +''' + + def __init__(self, data, title, url, root): + files = [] + items = [] + for c in data: + files.append(c['path']) + items.append(c['id']) + + files = list(set(files)) + items = list(set(items)) + self.files = files + self.clips = data + + self.fps = 25 + profile = profiles[self.fps == 25 and 'pal' or 'ntsc'] + + self.info = { + 'profile': profile, + 'root': root, + 'title': title, + 'url': url, + 'producers': '', + 'kdenlive_producers': '', + 'duration': 0, + 'stats': '%d clips from %d films' % (len(self.clips), len(items)) + } + self.add_title() + for clip in self.clips: + path = clip['path'] + if not path.startswith('/'): + path = os.path.join('/data/Cinema', clip['path']) + out = clip['out'] + if out-clip['in'] < MIN_DURATION: + out = clip['in'] + MIN_DURATION + duration = int((out-clip['in']) * self.fps) + self.add_clip(path, clip['in'], out) + self.info['duration'] += duration + + def render(self, output): + xml = output + with open(xml, 'w') as f: + self.info['playlist'] = '\n'.join(self.playlist) + data = self.template % self.info + f.write(data.strip()) + #os.system('melt -progress "%s" -consumer "avformat:%s"'%(xml, output)) + + def add_title(self): + self.info['duration'] = 5*self.fps + self.info['producers'] += ''' + + producer + %(duration)s + pause + + kdenlivetitle + <kdenlivetitle width="768" height="576" out="125" LC_NUMERIC="en_US.UTF-8"> + <item z-index="2" type="QGraphicsTextItem"> + <position x="86" y="123"> + <transform>1,0,0,0,1,0,0,0,1</transform> + </position> + <content font-color="255,255,255,255" font-outline-color="0,0,0,255" font-pixel-size="151" font-italic="0" alignment="4" font-underline="0" font-weight="50" font="Arial Black" font-outline="0.5">%(title)s</content> + </item> + <item z-index="1" type="QGraphicsTextItem"> + <position x="79" y="321"> + <transform>1,0,0,0,1,0,0,0,1</transform> + </position> + <content font-color="255,255,255,255" font-outline-color="0,0,0,255" font-pixel-size="24" font-italic="0" alignment="4" font-underline="0" font-weight="50" font="Arial Black" font-outline="0.5">%(url)s +%(stats)s</content> + </item> + <startviewport rect="0,0,768,576"/> + <endviewport rect="0,0,768,576"/> + <background color="0,0,0,0"/> + </kdenlivetitle> + + 0 + + ''' % self.info + self.playlist.append('' % (0, self.info['duration'])) + self.info['kdenlive_producers'] += ''' + + ''' % self.info + def add_producer(self, uri): + c = len(self.producers.keys()) + 2 + self.producers[uri] = c + info = ox.avinfo(uri) + i = { + 'id': c, + 'duration': int(info['duration'] * self.fps), + 'filename': uri.encode('utf-8'), + 'basename': os.path.basename(uri).encode('utf-8') + } + i['out'] = i['duration'] - 1 + self.info['producers'] += ''' + + producer + %(duration)s + pause + %(filename)s + 1 + avformat + + ''' % i + self.info['kdenlive_producers'] += ''' + + ''' % i + + def get_producer(self, uri): + if not uri in self.producers: + self.add_producer(uri) + return self.producers[uri] + + def add_clip(self, uri, start, end): + start = int(start * self.fps) + end = int(end * self.fps) - 1 + o = '' % (start, end, self.get_producer(uri)) + self.playlist.append(o) + +if __name__ == '__main__': + + if len(sys.argv) != 5: + print 'usage: %s