diff --git a/cccamp19/Marvel-Bold.ttf b/cccamp19/Marvel-Bold.ttf new file mode 100755 index 0000000..09be764 Binary files /dev/null and b/cccamp19/Marvel-Bold.ttf differ diff --git a/cccamp19/Marvel-Regular.ttf b/cccamp19/Marvel-Regular.ttf new file mode 100755 index 0000000..aa646b8 Binary files /dev/null and b/cccamp19/Marvel-Regular.ttf differ diff --git a/cccamp19/OFL.txt b/cccamp19/OFL.txt new file mode 100755 index 0000000..9a5b2b7 --- /dev/null +++ b/cccamp19/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2011, Carolina Trebol , +with Reserved Font Name "Marvel". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/cccamp19/config.ini b/cccamp19/config.ini new file mode 100644 index 0000000..838379d --- /dev/null +++ b/cccamp19/config.ini @@ -0,0 +1,31 @@ +[default] +template = cccamp19_talks_intro_1080p.mov + +[title] +in = 193 +out = 324 +font = Marvel-Bold.ttf +fontsize = 120 +fontcolor = #c68100 +x = (w-text_w)/2 +y = 480 + +[speaker] +in = 233 +out = 324 +font = Marvel-Regular.ttf +fontsize = 70 +fontcolor = #c68100 +x = (w-text_w)/2 +y = 845 + +[text] +in = 242 +out = 324 +font = Marvel-Regular.ttf +fontsize = 45 +fontcolor = #c68100 +x = (w-text_w)/2 +y = 927 +text = 'chaos communication camp 2019' + diff --git a/make-ffmpeg.py b/make-ffmpeg.py new file mode 100755 index 0000000..0b13287 --- /dev/null +++ b/make-ffmpeg.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 +# vim: tabstop=4 shiftwidth=4 expandtab + +import os +import sys +import subprocess +import renderlib +import argparse +import shlex +from PIL import ImageFont +from configparser import ConfigParser + +# Parse arguments +parser = argparse.ArgumentParser( + description='C3VOC Intro-Outro-Generator - Variant which renders only using video filters in ffmpeg', + usage="./make-ffmpeg.py yourproject/ https://url/to/schedule.xml", + formatter_class=argparse.RawTextHelpFormatter) + +parser.add_argument('project', action="store", metavar='Project folder', type=str, help=''' + Path to your project folder + ''') + +parser.add_argument('schedule', action="store", metavar='Schedule-URL', type=str, nargs='?', help=''' + URL or Path to your schedule.xml + ''') + +parser.add_argument('--debug', action="store_true", default=False, help=''' + Run script in debug mode and render with placeholder texts, + not parsing or accessing a schedule. Schedule-URL can be left blank when + used with --debug + This argument must not be used together with --id + Usage: ./make-ffmpeg.py yourproject/ --debug + ''') + +parser.add_argument('--id', dest='ids', nargs='+', action="store", type=int, help=''' + Only render the given ID(s) from your projects schedule. + This argument must not be used together with --debug + Usage: ./make-adobe-after-effects.py yourproject/ --id 4711 0815 4223 1337 + ''') + +parser.add_argument('--room', dest='rooms', nargs='+', action="store", type=str, help=''' + Only render the given room(s) from your projects schedule. + This argument must not be used together with --debug + Usage: ./make-adobe-after-effects.py yourproject/ --room "HfG_Studio" "ZKM_Vortragssaal" + ''') + +parser.add_argument('--skip', nargs='+', action="store", type=str, help=''' + Skip ID(s) not needed to be rendered. + Usage: ./make-ffmpeg.py yourproject/ --skip 4711 0815 4223 1337 + ''') + +parser.add_argument('--force', action="store_true", default=False, help=''' + Force render if file exists. + ''') + +args = parser.parse_args() + +if (args.skip is None): + args.skip = [] + + +def headline(str): + print("##################################################") + print(str) + print("##################################################") + print() + + +def error(str): + headline(str) + parser.print_help() + sys.exit(1) + + +if not (os.path.exists(os.path.join(args.project, 'config.ini'))): + error("config.ini file in Project Path is missing") + +if not args.project: + error("The Project Path is a required argument") + +if not args.debug and not args.schedule: + error("Either specify --debug or supply a schedule") + +if args.debug: + persons = ['Thomas Roth', 'Dmitry Nedospasov', 'Josh Datko'] + events = [{ + 'id': 'debug', + 'title': 'wallet.fail', + 'subtitle': 'Hacking the most popular cryptocurrency hardware wallets', + 'persons': persons, + 'personnames': ', '.join(persons), + 'room': 'Borg', + }] + +else: + events = list(renderlib.events(args.schedule)) + +parser = ConfigParser() +parser.read(os.path.join(os.path.dirname(args.project), 'config.ini')) +template = parser['default']['template'] + +title_in = parser['title']['in'] +title_out = parser['title']['out'] +title_font = parser['title']['font'] +title_fontsize = parser['title']['fontsize'] +title_fontcolor = parser['title']['fontcolor'] +title_x = parser['title']['x'] +title_y = parser['title']['y'] + +speaker_in = parser['speaker']['in'] +speaker_out = parser['speaker']['out'] +speaker_font = parser['speaker']['font'] +speaker_fontsize = parser['speaker']['fontsize'] +speaker_fontcolor = parser['speaker']['fontcolor'] +speaker_x = parser['speaker']['x'] +speaker_y = parser['speaker']['y'] + +text_in = parser['text']['in'] +text_out = parser['text']['out'] +text_font = parser['text']['font'] +text_fontsize = parser['text']['fontsize'] +text_fontcolor = parser['text']['fontcolor'] +text_x = parser['text']['x'] +text_y = parser['text']['y'] +text_text = parser['text']['text'] + +font_t = os.path.join(os.path.dirname(args.project), title_font) +font_s = os.path.join(os.path.dirname(args.project), speaker_font) +font_tt = os.path.join(os.path.dirname(args.project), text_font) + +fileformat = os.path.splitext(template)[1] +infile = os.path.join(os.path.dirname(args.project), template) + +def describe_event(event): + return "#{}: {}".format(event['id'], event['title']) + + +def event_print(event, message): + print("{} – {}".format(describe_event(event), message)) + + +def fmt_command(command, **kwargs): + args = {} + for key, value in kwargs.items(): + args[key] = shlex.quote(value) + + command = command.format(**args) + return shlex.split(command) + + +def run(command, **kwargs): + return subprocess.check_call( + fmt_command(command, **kwargs), + stderr=subprocess.STDOUT, + stdout=subprocess.DEVNULL) + + +def fit_text(string: str, frame_width): + split_line = [x.strip() for x in string.split()] + lines = "" + w = 0 + line_num = 0 + line = "" + for word in split_line: + w, _ = translation_font.getsize(" ".join([line, word])) + if w > (frame_width - (2 * 6)): + lines += line.strip() + "\n" + line = "" + + line += word + " " + + lines += line.strip() + return lines + + +def fit_title(string: str): + global translation_font + translation_font = ImageFont.truetype( + font_t, size=80, encoding="unic") + title = fit_text(string, 1080) + + return title + + +def fit_speaker(string: str): + global translation_font + translation_font = ImageFont.truetype( + font_s, size=50, encoding="unic") + speaker = fit_text(string, 1080) + + return speaker + + +def enqueue_job(event): + event_id = str(event['id']) + if event_id in args.skip: + event_print(event, "skipping " + str(event['id'])) + return + if (os.path.exists(os.path.join(args.project, event_id + '.ts')) or os.path.exists(os.path.join(args.project, event_id + '.mov'))) and not args.force: + event_print(event, "file exist, skipping " + str(event['id'])) + return + + event_title = str(event['title']) + event_personnames = str(event['personnames']) + + t = fit_title(event_title) + s = fit_speaker(event_personnames) + + if args.debug: + print('Title: ', t) + print('Speaker: ', s) + + if fileformat == '.mov': + outfile = os.path.join(os.path.dirname(args.project), event_id + '.mov') + else: + outfile = os.path.join(os.path.dirname(args.project), event_id + '.ts') + + videofilter = "drawtext=enable='between(n,{0},{1})':fontfile={2}:fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}',".format(title_in, title_out, font_t, title_fontsize, title_fontcolor, title_x, title_y, t) + videofilter += "drawtext=enable='between(n,{0},{1})':fontfile={2}:fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}',".format(speaker_in, speaker_out, font_s, speaker_fontsize, speaker_fontcolor, speaker_x, speaker_y, s) + videofilter += "drawtext=enable='between(n,{0},{1})':fontfile={2}:fontsize={3}:fontcolor={4}:x={5}:y={6}:text={7}".format(text_in, text_out, font_tt, text_fontsize, text_fontcolor, text_x, text_y, text_text) + + if fileformat == '.mov': + cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -vcodec prores_ks -pix_fmt yuva444p10le -profile:v 4444 -shortest -movflags faststart -f mov "{2}"'.format(infile, videofilter, outfile) + else: + cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -map 0:0 -c:v mpeg2video -q:v 2 -aspect 16:9 -map 0:1 -c:a mp2 -b:a 384k -shortest -f mpegts "{2}"'.format(infile, videofilter, outfile) + if args.debug: + print(cmd) + + run(cmd) + + return event_id + + +if args.ids: + if len(args.ids) == 1: + print("enqueuing {} job".format(len(args.ids))) + else: + print("enqueuing {} jobs".format(len(args.ids))) +else: + if len(events) == 1: + print("enqueuing {} job".format(len(events))) + else: + print("enqueuing {} jobs".format(len(events))) + + +for event in events: + if args.ids and event['id'] not in args.ids: + continue + + if args.rooms and event['room'] not in args.rooms: + print("skipping room %s (%s)" % (event['room'], event['title'])) + continue + + event_print(event, "enqueued as " + str(event['id'])) + + job_id = enqueue_job(event) + if not job_id: + event_print(event, "job was not enqueued successfully, skipping postprocessing") + continue + + +print('all done') + +