import re
import os
import sys
import codecs
import base64
import unicodecsv as csv
import shutil
from docx.shared import Inches
from egaia_config import getConfig
import egaia_sanitize
import egaia_make
import egaia_list
import egaia_meta
import egaia_parsefn
import utils
cmd_ffmpeg = getConfig('system', 'cmd_ffmpeg')
cmd_convert = getConfig('system', 'cmd_convert')
pub_path = getConfig('archive', 'pub_path')
[docs]def intertitle(text, clipdir):
"""Create an intertitle clip and return the clip name."""
title_name = egaia_sanitize.makeSlug(text[0:30])
image_file = os.path.join(clipdir, 'title_%s.png' % title_name)
clipname = 'title_%s.mp4' % title_name
out_clip = os.path.join(clipdir, clipname)
if os.path.exists(out_clip):
return clipname
r = utils.run([cmd_convert,
'-background', 'black',
'-fill', 'white',
'-pointsize', '96',
'-size', '1920x1080',
'-gravity', 'center',
'caption:%s' % text,
image_file
])
if not os.path.exists(image_file):
print "Error creating intertitle image"
return None
r2 = utils.run([cmd_ffmpeg,
'-f', 'lavfi',
'-i', 'anullsrc=channel_layout=stereo:sample_rate=48000',
'-loop', '1',
'-i', image_file,
'-shortest',
'-vcodec', 'libx264',
'-t', '4',
'-r', '24000/1001',
'-acodec', 'aac',
'-ab', '384K',
'-crf', '21',
'-bf', '2',
'-flags', '+cgop',
'-pix_fmt', 'yuv420p',
out_clip
])
return clipname
[docs]def processLine(clipdir, reel, clip, inpoint, outpoint, **kwargs):
"""Parse a line containing a clip name."""
# retrieve the source item from the PUB directory
video = egaia_list.listFiles( filter_type='df-h264',
uuid=reel,
filepath=pub_path )
if not video:
print "Matching video not found in the pub directory."
print "UUID: %s" % reel
return None
video = video[0]
# see if there is an offsets list
offsets_file = egaia_list.listFiles( filter_type='df-concat-offsets',
uuid=reel,
filepath=pub_path )
if offsets_file:
offsets_file = offsets_file[0]
target_clip_name = 'clip_%s_%s-%s.mp4' % (clip, inpoint, outpoint)
target_clip_path = os.path.join(clipdir, target_clip_name)
if offsets_file:
offset = None
# map to concatenated clip
with open(offsets_file, 'r') as db:
for line in db.readlines():
if clip in line:
offset = line.rpartition(',')[2]
break
if not offset:
print "could not find offset for %s in %s" % (clip, offsets_file)
return None
inpoint = str(float(inpoint) + float(offset))
outpoint = str(float(outpoint) + float(offset))
if not os.path.exists(target_clip_path):
r = utils.run([cmd_ffmpeg,
'-ss', inpoint,
'-i', video,
'-to', '6',
'-vcodec', 'libx264',
'-r', '24000/1001',
'-acodec', 'aac',
'-ab', '384K',
'-ar', '48000',
'-crf', '21',
'-bf', '2',
'-flags', '+cgop',
'-pix_fmt', 'yuv420p',
'-vf','scale=1920:1080:force_original_aspect_ratio=decrease,' +
'pad=1920:1080:(ow-iw)/2:(oh-ih)/2',
'-threads', getConfig('system', 'cores'),
target_clip_path
])
return target_clip_name
[docs]def get_dict(clip_list):
"""Read a csv file and return a dict."""
clips = list()
try:
with open(clip_list, mode='r') as clip_file:
reader = csv.DictReader(clip_file, encoding='utf-8')
for line in reader:
clips.append(line)
except:
print "Could not open file %s!" % clip_list
return None
return clips
[docs]def cat(clip_list):
"""Concatenate clips from the file clip_list. The format is:
#, reel, clip, inpoint, outpoint, title, caption, notes
"""
clips = get_dict(clip_list)
if not clips:
return
(base, uuid, ext) = egaia_parsefn.parseFilename(clip_list)
clipdir = '%s.df-clips.%s.dir' % (base, uuid)
print "Using clip dir: %s" % clipdir
if not os.path.exists(clipdir):
os.makedirs(clipdir)
# Make sure concat.txt file exists and is empty
with open(os.path.join(clipdir, 'concat.txt'), 'w') as cat:
cat.write(u"ffconcat version 1.0\n\n")
for line in clips:
title = line.get(u'title')
if title:
clipname = intertitle(title, clipdir)
else:
clipname = processLine(clipdir, **line)
if not clipname:
continue
with open(os.path.join(clipdir, 'concat.txt'), 'a') as cat:
cat.write("file '%s'\n" % clipname)
# use the title of the clips list
out_file = u'.'.join([base, u'df-h264', uuid, 'mp4'])
r = utils.run([cmd_ffmpeg,
'-f', 'concat',
'-segment_time_metadata', '1',
'-i', os.path.join(clipdir,'concat.txt'),
'-codec', 'copy',
'-acodec', 'copy',
out_file
])
if r:
shutil.move(out_file, os.path.dirname(clipdir))
[docs]def get_thumb(clipname):
"""Extract a thumbnail image from the clip."""
target = os.path.splitext(clipname)[0] + '.jpg'
if os.path.exists(target):
return target
# copy the main thumbnail image from halfway in
seek_time = float(egaia_meta.getDuration(clipname)) // 2
r = utils.run([cmd_ffmpeg, '-ss', '%s' % int(seek_time), '-i', clipname,
'-frames:v', '1', '-vf', 'scale=320:-1', target])
if r:
return target
return None
[docs]def make_finding_aid(clip_list, title='Video finding aid'):
"""Generate a finding aid with thumbnails and captions."""
clips = get_dict(clip_list)
if not clips:
return
(base, uuid, ext) = egaia_parsefn.parseFilename(clip_list)
clipdir = '%s.df-clips.%s.dir' % (base, uuid)
out_file = u'.'.join([base, u'df-video-description', uuid, 'docx'])
document = utils.makeDocument(uuid)
# LibreOffice uses "Heading3", not "Heading 3"
h3 = document.styles['Heading3']
document.add_heading(title, 0)
secs = 0
t = document.add_table(rows=0, cols=0)
t.add_column(Inches(3.5))
t.add_column(Inches(3))
for line in clips:
if line.get('title'):
# create a merged cell
r = t.add_row()
r.cells[0].merge(r.cells[1])
r.cells[0].text = line.get('title')
if not line.get('caption'):
continue
clipname = processLine(clipdir, **line)
if not clipname:
continue
r = t.add_row()
thumb = get_thumb(os.path.join(clipdir, clipname))
if thumb:
run = r.cells[0].paragraphs[0].add_run()
run.add_picture(thumb, width=Inches(3.3))
r.cells[1].text = utils.fmtTime(secs)
r.cells[1].add_paragraph(line.get('caption'))
dur = int(line.get('outpoint')) - int(line.get('inpoint'))
secs += dur
print "Writing to %s..." % out_file
document.save(out_file)
#~ # Make a PDF version of the finding aid.
#~ cmd_libreoffice = getConfig('system', 'cmd_libreoffice')
#~ if cmd_libreoffice:
#~ utils.run([ cmd_libreoffice,
#~ '--headless',
#~ '--convert-to', 'pdf',
#~ '--outdir', os.path.dirname(out_file),
#~ out_file])
return
def _cli(args):
"""egaia roughcut
Generate a rough-cut video out of a basic edit decision list, represented
in a csv format generated by ``egaia derive``.
Usage:
egaia roughcut --help
egaia roughcut [ --video ] [ --finding-aid ] [ --title=TITLE ] LIST
"""
if args['--video']:
cat(args['LIST'])
if args['--finding-aid']:
make_finding_aid(args['LIST'], title=args['--title'])