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