mlt generation
This commit is contained in:
commit
eeaf7d62ce
3 changed files with 362 additions and 0 deletions
4
README
Normal file
4
README
Normal file
|
@ -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
|
106
generate.py
Executable file
106
generate.py
Executable file
|
@ -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 <term> <output.json>' % 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)
|
252
timeline.py
Executable file
252
timeline.py
Executable file
|
@ -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': '<profile width="720" display_aspect_den="3" colorspace="601" frame_rate_den="1" description="DV/DVD PAL" height="576" display_aspect_num="4" frame_rate_num="25" progressive="0" sample_aspect_num="16" sample_aspect_den="15"/>'
|
||||||
|
}
|
||||||
|
|
||||||
|
class Video:
|
||||||
|
position = 0
|
||||||
|
playlist = []
|
||||||
|
producers = {}
|
||||||
|
|
||||||
|
template = '''<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<mlt title="%(title)s" version="0.8.0" root="%(root)s" LC_NUMERIC="en_US.UTF-8">
|
||||||
|
%(profile)s
|
||||||
|
<producer in="0" out="72869" id="black">
|
||||||
|
<property name="mlt_type">producer</property>
|
||||||
|
<property name="length">72870</property>
|
||||||
|
<property name="eof">pause</property>
|
||||||
|
<property name="resource">black</property>
|
||||||
|
<property name="aspect_ratio">0</property>
|
||||||
|
<property name="mlt_service">colour</property>
|
||||||
|
</producer>
|
||||||
|
<playlist id="black_track">
|
||||||
|
<entry in="0" out="12047" producer="black"/>
|
||||||
|
</playlist>
|
||||||
|
<playlist id="playlist1"/>
|
||||||
|
<playlist id="playlist2"/>
|
||||||
|
<playlist id="playlist3"/>
|
||||||
|
|
||||||
|
%(producers)s
|
||||||
|
|
||||||
|
<playlist id="playlist4">
|
||||||
|
%(playlist)s
|
||||||
|
</playlist>
|
||||||
|
<playlist id="playlist5"/>
|
||||||
|
<tractor title="%(title)s" global_feed="1" in="0" out="12047" id="maintractor">
|
||||||
|
<property name="meta.volume">1</property>
|
||||||
|
<track producer="black_track"/>
|
||||||
|
<track hide="video" producer="playlist1"/>
|
||||||
|
<track hide="video" producer="playlist2"/>
|
||||||
|
<track producer="playlist3"/>
|
||||||
|
<track producer="playlist4"/>
|
||||||
|
<track producer="playlist5"/>
|
||||||
|
<transition id="transition0">
|
||||||
|
<property name="a_track">1</property>
|
||||||
|
<property name="b_track">2</property>
|
||||||
|
<property name="mlt_type">transition</property>
|
||||||
|
<property name="mlt_service">mix</property>
|
||||||
|
<property name="always_active">1</property>
|
||||||
|
<property name="combine">1</property>
|
||||||
|
<property name="internal_added">237</property>
|
||||||
|
</transition>
|
||||||
|
<transition id="transition1">
|
||||||
|
<property name="a_track">1</property>
|
||||||
|
<property name="b_track">3</property>
|
||||||
|
<property name="mlt_type">transition</property>
|
||||||
|
<property name="mlt_service">mix</property>
|
||||||
|
<property name="always_active">1</property>
|
||||||
|
<property name="combine">1</property>
|
||||||
|
<property name="internal_added">237</property>
|
||||||
|
</transition>
|
||||||
|
<transition id="transition2">
|
||||||
|
<property name="a_track">1</property>
|
||||||
|
<property name="b_track">4</property>
|
||||||
|
<property name="mlt_type">transition</property>
|
||||||
|
<property name="mlt_service">mix</property>
|
||||||
|
<property name="always_active">1</property>
|
||||||
|
<property name="combine">1</property>
|
||||||
|
<property name="internal_added">237</property>
|
||||||
|
</transition>
|
||||||
|
<transition id="transition3">
|
||||||
|
<property name="a_track">1</property>
|
||||||
|
<property name="b_track">5</property>
|
||||||
|
<property name="mlt_type">transition</property>
|
||||||
|
<property name="mlt_service">mix</property>
|
||||||
|
<property name="always_active">1</property>
|
||||||
|
<property name="combine">1</property>
|
||||||
|
<property name="internal_added">237</property>
|
||||||
|
</transition>
|
||||||
|
</tractor>
|
||||||
|
<kdenlivedoc profile="dv_pal" kdenliveversion="0.9.2" version="0.88" projectfolder="%(root)s">
|
||||||
|
<customeffects/>
|
||||||
|
<documentproperties proxyimageminsize="2000" zonein="0" enableproxy="0" zoneout="100" generateproxy="0" zoom="9" verticalzoom="1" proxyextension="ts" position="1" documentid="1353193897205" generateimageproxy="0" proxyminsize="1000" proxyparams="-f mpegts -acodec libmp3lame -ac 2 -ab 128k -ar 48000 -vcodec mpeg2video -g 5 -deinterlace -s 480x270 -vb 400k"/>
|
||||||
|
<documentmetadata/>
|
||||||
|
<documentnotes><!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></documentnotes>
|
||||||
|
<profileinfo width="720" display_aspect_den="3" frame_rate_den="1" description="DV/DVD PAL" height="576" frame_rate_num="25" display_aspect_num="4" progressive="0" sample_aspect_num="16" sample_aspect_den="15"/>
|
||||||
|
<tracksinfo>
|
||||||
|
<trackinfo blind="1" mute="0" locked="0" trackname="Audio 2" type="audio"/>
|
||||||
|
<trackinfo blind="1" mute="0" locked="0" trackname="Audio 1" type="audio"/>
|
||||||
|
<trackinfo blind="0" mute="0" locked="0" trackname="Video 3"/>
|
||||||
|
<trackinfo blind="0" mute="0" locked="0" trackname="Video 2"/>
|
||||||
|
<trackinfo blind="0" mute="0" locked="0" trackname="Video 1"/>
|
||||||
|
</tracksinfo>
|
||||||
|
%(kdenlive_producers)s
|
||||||
|
<markers/>
|
||||||
|
<groups/>
|
||||||
|
</kdenlivedoc>
|
||||||
|
</mlt>
|
||||||
|
'''
|
||||||
|
|
||||||
|
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 in="0" out="%(duration)s" id="1">
|
||||||
|
<property name="mlt_type">producer</property>
|
||||||
|
<property name="length">%(duration)s</property>
|
||||||
|
<property name="eof">pause</property>
|
||||||
|
<property name="resource"/>
|
||||||
|
<property name="mlt_service">kdenlivetitle</property>
|
||||||
|
<property name="xmldata"><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>
|
||||||
|
</property>
|
||||||
|
<property name="force_reload">0</property>
|
||||||
|
</producer>
|
||||||
|
''' % self.info
|
||||||
|
self.playlist.append('<entry in="%s" out="%s" producer="1"/>' % (0, self.info['duration']))
|
||||||
|
self.info['kdenlive_producers'] += '''
|
||||||
|
<kdenlive_producer audio_max="0" id="1" default_video="0" xmldata="<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>
" name="Title" in="0" thumbnail="100" transparency="1" default_audio="0" duration="125" aspect_ratio="0.000000" channels="0" frequency="0" out="125" video_max="0" progressive="0" type="6" frame_size="720x576"/>
|
||||||
|
''' % 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 in="0" out="%(out)s" id="%(id)s_1">
|
||||||
|
<property name="mlt_type">producer</property>
|
||||||
|
<property name="length">%(duration)s</property>
|
||||||
|
<property name="eof">pause</property>
|
||||||
|
<property name="resource">%(filename)s</property>
|
||||||
|
<property name="seekable">1</property>
|
||||||
|
<property name="mlt_service">avformat</property>
|
||||||
|
</producer>
|
||||||
|
''' % i
|
||||||
|
self.info['kdenlive_producers'] += '''
|
||||||
|
<kdenlive_producer
|
||||||
|
id="%(id)s"
|
||||||
|
name="%(basename)s"
|
||||||
|
resource="%(filename)s"
|
||||||
|
/>
|
||||||
|
''' % 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 = '<entry in="%s" out="%s" producer="%d_1"/>' % (start, end, self.get_producer(uri))
|
||||||
|
self.playlist.append(o)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
if len(sys.argv) != 5:
|
||||||
|
print 'usage: %s <json input> <video prefix> <output> <title>' % sys.argv[0]
|
||||||
|
sys.exit(1)
|
||||||
|
json_input = sys.argv[1]
|
||||||
|
video_prefix = sys.argv[2]
|
||||||
|
root = sys.argv[3]
|
||||||
|
title = sys.argv[4]
|
||||||
|
output = json_input + '.kdenlive'
|
||||||
|
url = 'http://pan.do/ra'
|
||||||
|
|
||||||
|
data = json.load(open(json_input))
|
||||||
|
c = Video(data,
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
root)
|
||||||
|
c.render(output)
|
Loading…
Reference in a new issue