Source code for egaia.egaia_roughcut

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'])