pandora_cdosea/render.py

451 lines
14 KiB
Python
Raw Normal View History

2017-01-04 17:26:18 +00:00
#!/usr/bin/python3
import os
2017-02-17 20:17:55 +00:00
import sys
2017-01-04 17:26:18 +00:00
import json
import subprocess
2017-02-17 21:42:57 +00:00
from collections import defaultdict
2017-01-04 17:26:18 +00:00
import string
2017-02-17 20:17:55 +00:00
from glob import glob
2017-01-04 17:26:18 +00:00
from pi import random
from keywords import KEYWORDS
import ox
import ox.web.auth
2017-03-02 23:34:49 +00:00
base_url = 'http://127.0.0.1:2620'
2017-01-04 17:26:18 +00:00
2017-02-17 21:42:57 +00:00
FRAME_DURATION = 1/60
2017-05-16 12:59:51 +00:00
MAX_DURATION = 40
2017-02-17 21:42:57 +00:00
2017-03-06 08:36:39 +00:00
HIDDEN_TAGS = [
2017-05-16 12:59:51 +00:00
"women with white males",
"gene z hanrahan"
2017-03-06 08:36:39 +00:00
]
2017-03-20 01:30:09 +00:00
# items to not use at all
BLACKLIST = [
'XN'
]
2017-01-04 17:26:18 +00:00
api = None
def get_api():
global api
if not api:
api = ox.API(base_url + '/api/')
api.signin(**ox.web.auth.get('cdosea'))
if os.path.exists('PATHS.json'):
PATHS = json.load(open('PATHS.json'))
else:
PATHS = {}
if os.path.exists('CLIPS.json'):
CLIPS = json.load(open('CLIPS.json'))
else:
CLIPS = {}
2017-02-17 21:42:57 +00:00
if not os.path.exists('MUSIC.json'):
MUSIC = defaultdict(list)
for letter in os.listdir('music'):
for d in range(10):
path = os.path.join('music', letter, '%d.mp3' % d)
MUSIC[letter].append({
'path': path,
'duration': ox.avinfo(path)['duration']
})
with open('MUSIC.json', 'w') as fd:
2017-03-01 09:20:49 +00:00
json.dump(MUSIC, fd, indent=2, sort_keys=True)
2017-02-17 21:42:57 +00:00
else:
MUSIC = json.load(open('MUSIC.json'))
if not os.path.exists('VOCALS.json'):
VOCALS = defaultdict(list)
for letter in os.listdir('vocals'):
for fn in sorted(os.listdir(os.path.join('vocals', letter))):
path = os.path.join('vocals', letter, fn)
VOCALS[letter].append({
'path': path,
'duration': ox.avinfo(path)['duration']
})
2017-03-06 08:36:39 +00:00
while len(VOCALS[letter]) < 10:
VOCALS[letter] += VOCALS[letter]
VOCALS[letter] = VOCALS[letter][:10]
2017-02-17 21:42:57 +00:00
with open('VOCALS.json', 'w') as fd:
2017-03-01 09:20:49 +00:00
json.dump(VOCALS, fd, indent=2, sort_keys=True)
2017-02-17 21:42:57 +00:00
else:
VOCALS = json.load(open('VOCALS.json'))
2017-03-15 10:23:43 +00:00
if not os.path.exists('DRONES.json'):
DRONES = defaultdict(list)
2017-05-16 12:59:51 +00:00
prefix = 'drones_wav'
2017-05-14 13:46:07 +00:00
for letter in os.listdir(prefix):
for fn in sorted(os.listdir(os.path.join(prefix, letter))):
path = os.path.join(prefix, letter, fn)
2017-03-15 10:23:43 +00:00
DRONES[letter[0]].append({
'path': path,
'duration': ox.avinfo(path)['duration']
})
with open('DRONES.json', 'w') as fd:
json.dump(DRONES, fd, indent=2, sort_keys=True)
else:
DRONES = json.load(open('DRONES.json'))
2017-01-04 17:26:18 +00:00
def get_path(id):
global PATHS
if id not in PATHS:
get_api()
info = api.findMedia({
'query': {
'conditions': [
{'key': 'id', 'operator': '==', 'value': id}
]
},
'keys': ['id', 'extension'],
'range': [0, 1]
})['data']['items'][0]
path = os.path.join('cache', '%s.%s' % (info['id'], info['extension']))
2017-02-17 20:17:55 +00:00
h = info['id']
source = '/srv/pandora/data/media/%s/%s/%s/%s/data.*' % (
h[:2], h[2:4], h[4:6], h[6:]
)
source = glob(source)[0]
2017-01-04 17:26:18 +00:00
if not os.path.exists(path):
2017-02-17 20:17:55 +00:00
if not os.path.exists(source):
print('WTF', source)
sys.exit(1)
os.symlink(source, path)
'''
2017-01-04 17:26:18 +00:00
url = '%s/%s/download/source/' % (base_url, id)
print('get video', url)
2017-02-17 20:17:55 +00:00
'''
2017-01-04 17:26:18 +00:00
PATHS[id] = path
with open('PATHS.json', 'w') as fd:
json.dump(PATHS, fd, indent=4, sort_keys=True)
return PATHS[id]
def get_clips(tag):
global CLIPS
if tag not in CLIPS:
get_api()
clips = api.findAnnotations({
'query': {
'conditions': [
{'key': 'layer', 'operator': '==', 'value': 'keywords'},
{'key': 'value', 'operator': '==', 'value': tag}
],
'operator': '&'
},
'keys': ['id', 'in', 'out'],
2017-03-20 01:30:09 +00:00
'range': [0, 90000]})['data']['items']
clips = [clip for clip in clips if clip['id'].split('/')[0] not in BLACKLIST]
2017-01-04 17:26:18 +00:00
for clip in clips:
clip['path'] = get_path(clip['id'].split('/')[0])
2017-02-17 21:42:57 +00:00
# or use round?
clip['in'] = int(clip['in'] / FRAME_DURATION) * FRAME_DURATION
clip['out'] = int(clip['out'] / FRAME_DURATION) * FRAME_DURATION
2017-01-04 17:26:18 +00:00
clip['duration'] = clip['out'] - clip['in']
clip['tag'] = tag
2017-05-16 12:59:51 +00:00
clips = [clip for clip in clips if clip['duration'] and clip['duration'] <= MAX_DURATION]
2017-03-20 16:07:21 +00:00
for clip in clips:
fduration = ox.avinfo(clip['path'])['duration']
if clip['out'] > fduration:
if clip['in'] == 0:
clip['out'] = fduration
clip['duration'] = clip['out'] - clip['in']
else:
print('FAIL', clip, fduration)
sys.exit(1)
2017-01-04 17:26:18 +00:00
CLIPS[tag] = list(sorted(clips, key=lambda c: c['id']))
with open('CLIPS.json', 'w') as fd:
json.dump(CLIPS, fd, indent=4, sort_keys=True)
2017-03-02 21:09:22 +00:00
return CLIPS[tag].copy()
2017-01-04 17:26:18 +00:00
def random_choice(seq, items):
n = n_ = len(items) - 1
2017-02-17 20:17:55 +00:00
#print('len', n)
2017-01-04 17:26:18 +00:00
if n == 0:
return items[0]
r = seq()
base = 10
while n > 10:
n /= 10
2017-02-17 20:17:55 +00:00
#print(r)
2017-01-04 17:26:18 +00:00
r += seq()
base += 10
r = int(n_ * r / base)
2017-02-17 20:17:55 +00:00
#print('result', r, items)
2017-01-04 17:26:18 +00:00
return items[r]
def splitint(number, by):
div = int(number/by)
mod = number % by
return [div + 1 if i > (by - 1 - mod) else div for i in range(by)]
def filter_clips(clips, duration, max_duration=0):
# 1 minute
blur = 0.5
low = 1
high = 10
# 2 minute
blur = 1
low = 2
high = 20
buckets = {}
clips_ = []
for tag in clips:
for clip in clips[tag]:
clip['tag'] = tag
clips_.append(clip)
clips_.sort(key=lambda c: c['duration'])
2017-02-17 20:17:55 +00:00
#print(clips_)
2017-01-04 17:26:18 +00:00
size = splitint(len(clips_), 10)
p = 0
for i in range(10):
buckets[i+1] = clips_[p:+p+size[i]]
p += size[i]
clips_ = {}
2017-02-17 20:17:55 +00:00
#print(buckets[duration])
2017-01-04 17:26:18 +00:00
for clip in buckets[duration]:
if clip['tag'] not in clips_:
clips_[clip['tag']] = []
clips_[clip['tag']].append(clip)
return clips_
2017-05-16 12:59:51 +00:00
def add_blank(track, d):
if track and track[-1].get('blank'):
track[-1]['duration'] += d
else:
blank = {'blank': True, 'duration': d}
track.append(blank)
return d
2017-01-04 17:26:18 +00:00
def sequence(seq, letter):
2017-05-16 12:59:51 +00:00
2017-01-04 17:26:18 +00:00
tags = KEYWORDS[letter]
clips = {tag: get_clips(tag) for tag in tags}
all_clips = clips.copy()
2017-01-04 17:26:18 +00:00
result = {
'clips': [],
'text': [],
2017-02-17 21:42:57 +00:00
'vocals': [],
'music': [],
2017-03-15 10:23:43 +00:00
'drones0': [],
'drones1': [],
2017-01-04 17:26:18 +00:00
}
duration = 0
2017-03-02 21:10:54 +00:00
MAX_DURATION = 60 * 2 + 5
MIN_DURATION = 60 * 2 - 4
2017-05-16 12:59:51 +00:00
# add 1 black frame for sync playback
duration = 1 * FRAME_DURATION
result['clips'].append({'black': True, 'duration': duration})
2017-01-04 17:26:18 +00:00
while duration < MAX_DURATION and not duration >= MIN_DURATION:
# clip duration: 1-10
n = seq()
if n == 0:
n = 10
max_duration = MAX_DURATION - duration
clips_n = filter_clips(clips, n, max_duration)
tags_n = [tag for tag in tags if tag in clips_n]
if not tags_n:
print('MISSING CLIPS, fall back to all clips!', letter, n)
clips_n = filter_clips(all_clips, n, max_duration)
tags_n = [tag for tag in tags if tag in clips_n]
2017-01-04 17:26:18 +00:00
if not tags_n:
print('NO tags for', letter, n)
2017-02-17 20:17:55 +00:00
sys.exit(1)
2017-01-04 17:26:18 +00:00
tag = random_choice(seq, tags_n)
#if 'tiger' in tags_n:
# tag = 'tiger'
clip = random_choice(seq, clips_n[tag])
duration += clip['duration']
result['clips'].append(clip.copy())
# no reuse
for t in clips:
clips[t] = [c for c in clips[t] if not c['id'] == clip['id']]
2017-03-02 21:10:54 +00:00
'''
2017-01-04 17:26:18 +00:00
for clip in result['clips']:
if seq() == 0:
clip['black'] = True
2017-03-02 21:10:54 +00:00
'''
2017-02-17 21:42:57 +00:00
# text overlay
2017-01-04 17:26:18 +00:00
position = last_text = 0
2017-02-17 20:17:55 +00:00
tags_text = []
2017-05-16 12:59:51 +00:00
# no overlay for the first 2 frames
position = last_text = add_blank(result['text'], 2 * FRAME_DURATION)
2017-01-04 17:26:18 +00:00
while position < duration:
n = seq()
if n == 0:
blank = {'blank': True, 'duration': position - last_text}
2017-03-20 01:30:09 +00:00
if blank['duration']:
result['text'].append(blank)
2017-01-04 17:26:18 +00:00
n = seq()
if n == 0:
n = 10
n = min(n, duration-position)
2017-02-17 20:17:55 +00:00
if not tags_text:
tags_text = list(sorted(set(tags)))
2017-03-06 08:36:39 +00:00
tags_text = [t for t in tags_text if t not in HIDDEN_TAGS]
2017-02-17 20:17:55 +00:00
ttag = random_choice(seq, tags_text)
tags_text.remove(ttag)
2017-01-04 17:26:18 +00:00
text = {
2017-02-17 20:17:55 +00:00
'text': ttag,
2017-01-04 17:26:18 +00:00
'duration': n
}
result['text'].append(text)
position += n
last_text = position
else:
position += n
if last_text < duration:
blank = {'blank': True, 'duration': duration - last_text}
result['text'].append(blank)
2017-02-22 16:30:55 +00:00
2017-02-17 21:42:57 +00:00
# music
2017-02-22 16:30:55 +00:00
track = 'music'
2017-02-17 21:42:57 +00:00
if letter in MUSIC:
2017-02-22 16:30:55 +00:00
position = 0
2017-02-17 21:42:57 +00:00
while position < duration:
n = seq()
2017-02-22 16:30:55 +00:00
if n < 5:
n = seq()
position += add_blank(result[track], min(n, duration-position))
else:
2017-02-17 21:42:57 +00:00
n = seq()
clip = MUSIC[letter][n]
position += clip['duration']
2017-03-01 09:20:49 +00:00
if result[track] and position > duration \
2017-02-22 16:30:55 +00:00
and result[track][-1].get('blank') \
and result[track][-1]['duration'] > clip['duration']:
result[track][-1]['duration'] -= (position-duration)
2017-02-17 21:42:57 +00:00
position = duration
2017-03-20 01:30:09 +00:00
if not result[track][-1]['duration']:
result[track].pop(-1)
2017-02-22 16:30:55 +00:00
if position <= duration:
result[track].append(clip)
else:
position -= clip['duration']
break
if position < duration:
position += add_blank(result[track], duration - position)
2017-02-17 21:42:57 +00:00
# vocals
2017-02-22 16:30:55 +00:00
track = 'vocals'
if letter in VOCALS:
position = 0
2017-03-20 01:30:09 +00:00
loop = 0
2017-02-22 16:30:55 +00:00
while position < duration:
n = seq()
2017-03-20 01:30:09 +00:00
# vocals should start after one blank
if len(result[track]) and result[track][-1].get('blank'):
n = 10
# 50 % chance of a silence of up to 5 seconds
2017-02-22 16:30:55 +00:00
if n < 5:
2017-03-20 01:30:09 +00:00
n = seq() / 2 # (0 - 5 seconds)
2017-02-22 16:30:55 +00:00
position += add_blank(result[track], min(n, duration-position))
else:
n = seq()
clip = VOCALS[letter][n]
position += clip['duration']
2017-03-01 09:20:49 +00:00
if result[track] and position > duration \
2017-02-22 16:30:55 +00:00
and result[track][-1].get('blank') \
and result[track][-1]['duration'] > clip['duration']:
result[track][-1]['duration'] -= (position-duration)
position = duration
2017-03-20 01:30:09 +00:00
if not result[track][-1]['duration']:
result[track].pop(-1)
2017-02-22 16:30:55 +00:00
if position <= duration:
result[track].append(clip)
else:
position -= clip['duration']
2017-03-20 01:30:09 +00:00
if duration - position < 10 or loop > 10:
break
loop += 1
2017-02-22 16:30:55 +00:00
if position < duration:
position += add_blank(result[track], duration - position)
'''
2017-02-17 21:42:57 +00:00
if letter in VOCALS:
n = seq()
clip = VOCALS[letter][n]
n = 1.0 / (seq() + 1) # 0.1 - 1
silence = duration - clip['duration']
silence_start = n * silence
blank = {'blank': True, 'duration': silence_start}
if n != 0:
result['vocals'].append(blank)
result['vocals'].append(clip)
if n != 1:
blank = {'blank': True, 'duration': silence - silence_start}
result['vocals'].append(blank)
2017-02-22 16:30:55 +00:00
'''
2017-03-15 10:23:43 +00:00
# drones
if letter in DRONES:
for track in ('drones0', 'drones1'):
position = 0
while position < duration:
n = seq()
if n == 9:
position += add_blank(result[track], min(3.141, duration - position))
else:
clip = DRONES[letter][n]
2017-03-15 12:00:36 +00:00
if position + clip['duration'] < duration:
position += clip['duration']
result[track].append(clip)
else:
c = clip.copy()
c['duration'] = duration - position
result[track].append(c)
position += c['duration']
2017-03-15 10:23:43 +00:00
if position < duration:
position += add_blank(result[track], duration - position)
2017-02-17 21:42:57 +00:00
2017-02-22 16:30:55 +00:00
for track in result:
if result[track]:
tduration = sum([c['duration'] for c in result[track]])
if not abs(tduration - duration) < 0.000001:
2017-05-16 12:59:51 +00:00
raise Exception('invalid duration on track: %s %s vs %s %s' % (track, tduration, duration, result[track]))
2017-01-04 17:26:18 +00:00
return result
2017-02-17 22:07:43 +00:00
2017-01-04 17:26:18 +00:00
if __name__ == '__main__':
2017-05-16 12:59:51 +00:00
render_xml = len(sys.argv) < 2 or sys.argv[1] != 'json'
2017-01-04 17:26:18 +00:00
for n in range(10):
2017-02-17 20:17:55 +00:00
seq = random(n * 1000)
#for letter in ('T', 'W'):
for letter in string.ascii_uppercase:
2017-01-04 17:26:18 +00:00
r = sequence(seq, letter)
2017-02-17 20:17:55 +00:00
tjson = 'output/%02d/%s.json' % (n, letter)
folder = os.path.dirname(tjson)
if not os.path.exists(folder):
ox.makedirs(folder)
if os.path.exists(tjson):
with open(tjson, 'r') as fd:
old = fd.read()
else:
old = None
current = json.dumps(r, indent=4, sort_keys=True)
#print(current)
2017-01-04 17:26:18 +00:00
#print(sum([c['duration'] for c in r['clips']]))
2017-02-17 20:17:55 +00:00
if current != old:
with open(tjson, 'w') as fd:
fd.write(current)
2017-05-16 12:59:51 +00:00
if render_xml:
2017-03-20 01:30:09 +00:00
if current != old or os.path.getmtime(tjson) < os.path.getmtime('render_mlt.py'):
2017-05-16 12:59:51 +00:00
subprocess.call(['./render_mlt.py', tjson])
2017-03-20 01:30:09 +00:00
#subprocess.call(['./render_audio.py', tjson])