diff --git a/camp2023/config.ini b/camp2023/config.ini index e8c7ab6..2072250 100644 --- a/camp2023/config.ini +++ b/camp2023/config.ini @@ -1,13 +1,15 @@ -[meta] +[default] schedule = https://pretalx.c3voc.de/camp2023/schedule/export/schedule.xml template = intro.mp4 alpha = false prores = false -inout_type = t +fontfile = true +inout = t [title] in = 16 out = 24 +fontfamily = BeonRegular fontfile = saira.ttf fontsize = 100 fontcolor = #FB48C4 @@ -17,6 +19,7 @@ y = 100 [speaker] in = 16 out = 24 +fontfamily = BeonRegular fontfile = saira.ttf fontsize = 70 fontcolor = #3FFF21 @@ -26,10 +29,11 @@ y = 800 [text] in = 16 out = 24 +fontfamily = BeonRegular fontfile = saira.ttf fontsize = 45 fontcolor = #FB48C4 x = (w-text_w)/2 y = 1000 -text = Chaos Communication Camp 2023 +text = 'Chaos Communication Camp 2023' diff --git a/cccamp19/config.ini b/cccamp19/config.ini index d723d7d..181caed 100644 --- a/cccamp19/config.ini +++ b/cccamp19/config.ini @@ -1,13 +1,15 @@ -[meta] +[default] schedule = https://fahrplan.events.ccc.de/camp/2019/Fahrplan/schedule.xml template = cccamp19_talks_intro_1080p.mov alpha = true prores = true -inout_type = n +fontfile = true +inout = n [title] in = 193 out = 324 +fontfamily = fontfile = Marvel-Bold.ttf fontsize = 120 fontcolor = #c68100 @@ -17,6 +19,7 @@ y = 480 [speaker] in = 233 out = 324 +fontfamily = fontfile = Marvel-Regular.ttf fontsize = 70 fontcolor = #c68100 @@ -26,10 +29,11 @@ y = 845 [text] in = 242 out = 324 +fontfamily = fontfile = Marvel-Regular.ttf fontsize = 45 fontcolor = #c68100 x = (w-text_w)/2 y = 927 -text = chaos communication camp 2019 +text = 'chaos communication camp 2019' diff --git a/denog11/config.ini b/denog11/config.ini index 89c8096..6f403b5 100644 --- a/denog11/config.ini +++ b/denog11/config.ini @@ -1,13 +1,15 @@ -[meta] +[default] schedule = https://pretalx.denog.de/denog11/schedule/export/schedule.xml template = denog11_intro_template.ts alpha = false prores = false -inout_type = n +fontfile = true +inout = n [title] in = 1 out = 6.5 +fontfamily = fontfile = DejaVuSans.ttf fontsize = 100 fontcolor = #f9cc12 @@ -17,6 +19,7 @@ y = 200 [speaker] in = 2 out = 6.5 +fontfamily = fontfile = DejaVuSans.ttf fontsize = 60 fontcolor = #ffffff @@ -26,10 +29,11 @@ y = 900 [text] in = 3 out = 6.5 +fontfamily = fontfile = DejaVuSans.ttf fontsize = 45 fontcolor = #ffffff x = 640 y = 1000 -; text = +text = '' diff --git a/fossgis24/__init__.py b/fossgis24/__init__.py new file mode 100644 index 0000000..86cc8e2 --- /dev/null +++ b/fossgis24/__init__.py @@ -0,0 +1,201 @@ +#!/usr/bin/python3 + +import subprocess +from renderlib import * +from schedulelib import * +from easing import * + +# URL to Schedule-XML +scheduleUrl = 'https://pretalx.com/fossgis2024/schedule/export/schedule.xml' + +# For (really) too long titles +titlemap = { + #708: "Neue WEB-Anwendungen des LGRB Baden-Württemberg im Überblick" +} + + +def outroFrames(params): + # 8 Sekunden + + # 2 Sekunden Fadein Text + frames = 2*fps + for i in range(0, frames): + yield ( + ('banderole', 'style', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames) ), + ('license', 'style', 'opacity', 0) + ) + + # 2 Sekunde Fadein Lizenz-Logo + frames = 2*fps + for i in range(0, frames): + yield ( + ('banderole', 'style', 'opacity', 1), + ('license', 'style', 'opacity', "%.4f" % (float(i)/frames)) + ) + + # 4 Sekunde stehen bleiben + frames = 4*fps + for i in range(0, frames): + yield ( + ('banderole', 'style', 'opacity', 1), + ('license', 'style', 'opacity', 1) + ) + +def introFrames(params): + # 7 Sekunden + + # 2 Sekunden Text 1 + frames = 2*fps + for i in range(0, frames): + yield ( + ('box-und-text1', 'style', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)), + ('url', 'style', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)), + ('text1', 'style', 'opacity', "%.4f" % 1), + ('text2', 'style', 'opacity', 0) + ) + + # 1 Sekunde Fadeout Text 1 + frames = 1*fps + for i in range(0, frames): + yield ( + ('box-und-text1', 'style', 'opacity', 1), + ('url', 'style', 'opacity', 1), + ('text1', 'style', 'opacity', "%.4f" % (1-(float(i)/frames))), + ('text2', 'style', 'opacity', 0) + ) + + # 2 Sekunden Text 2 + frames = 2*fps + for i in range(0, frames): + yield ( + ('box-und-text1', 'style', 'opacity', 1), + ('url', 'style', 'opacity', 1), + ('text1', 'style', 'opacity', 0), + ('text2', 'style', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)) + ) + + # 2 Sekunden stehen bleiben + frames = 2*fps + for i in range(0, frames): + yield ( + ('box-und-text1', 'style', 'opacity', 1), + ('url', 'style', 'opacity', 1), + ('text1', 'style', 'opacity', 0), + ('text2', 'style', 'opacity', 1) + ) + +def pauseFrames(params): + # 12 Sekunden + + # 2 Sekunden Text1 stehen + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'style', 'opacity', 1), + ('text2', 'style', 'opacity', 0) + ) + + # 2 Sekunden Fadeout Text1 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'style', 'opacity', "%.4f" % (1-easeOutCubic(i, 0, 1, frames))), + ('text2', 'style', 'opacity', 0) + ) + + # 2 Sekunden Fadein Text2 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'style', 'opacity', 0), + ('text2', 'style', 'opacity', "%.4f" % easeOutCubic(i, 0, 1, frames)) + ) + + # 2 Sekunden Text2 stehen + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'style', 'opacity', 0), + ('text2', 'style', 'opacity', 1) + ) + + # 2 Sekunden Fadeout Text2 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'style', 'opacity', 0), + ('text2', 'style', 'opacity', "%.4f" % (1-easeOutCubic(i, 0, 1, frames))) + ) + + # 2 Sekunden Fadein Text1 + frames = 2*fps + for i in range(0, frames): + yield ( + ('text1', 'style', 'opacity', "%.4f" % (easeOutCubic(i, 0, 1, frames))), + ('text2', 'style', 'opacity', 0) + ) + +def debug(): + render( + 'intro.svg', + '../intro.ts', + introFrames, + { + '$id': 904, + '$title': 'Was ist Open Source, wie funktioniert das?', + '$subtitle': 'Die Organisation der Open Geo- und GIS-Welt. Worauf man achten sollte.', + '$personnames': 'Arnulf Christl, Astrid Emde, Dominik Helle, Till Adams' + } + ) + + render( + 'outro.svg', + '../outro.ts', + outroFrames + ) + + render('pause.svg', + '../pause.ts', + pauseFrames + ) + +def tasks(queue, args, idlist, skiplist): + # iterate over all events extracted from the schedule xml-export + for event in events(scheduleUrl): + if event['room'] not in ('Hörsaal 1 (Audimax 1)', 'Hörsaal 2 (Ditze H016)', 'Hörsaal 3 (K0506/ Audimax 2)', 'Hörsaal 4 (A.013)'): + print("skipping room %s (%s)" % (event['room'], event['title'])) + continue + + + if (event['id'] in idlist or not idlist) and not 'intro' in skiplist: + # generate a task description and put them into the queue + queue.put(Rendertask( + infile = 'intro.svg', + outfile = str(event['id'])+".ts", + sequence = introFrames, + parameters = { + '$id': event['id'], + '$title': event['title'], + '$url': event['url'], + #'$subtitle': event['subtitle'], + '$personnames': event['personnames'] + } + )) + + if not 'outro' in skiplist: + # place a task for the outro into the queue + queue.put(Rendertask( + infile = 'outro.svg', + outfile = 'outro.ts', + sequence = outroFrames + )) + + if not 'pause' in skiplist: + # place the pause-sequence into the queue + queue.put(Rendertask( + infile = 'pause.svg', + outfile = 'pause.ts', + sequence = pauseFrames + )) + + diff --git a/fossgis24/artwork/001_camptocamp_logo.png b/fossgis24/artwork/001_camptocamp_logo.png new file mode 100644 index 0000000..17be0fc Binary files /dev/null and b/fossgis24/artwork/001_camptocamp_logo.png differ diff --git a/fossgis24/artwork/002_WhereGroup.png b/fossgis24/artwork/002_WhereGroup.png new file mode 100644 index 0000000..36f2cc7 Binary files /dev/null and b/fossgis24/artwork/002_WhereGroup.png differ diff --git a/fossgis24/artwork/002_WhereGroup.psd b/fossgis24/artwork/002_WhereGroup.psd new file mode 100644 index 0000000..bf28598 Binary files /dev/null and b/fossgis24/artwork/002_WhereGroup.psd differ diff --git a/fossgis24/artwork/003_dataport.png b/fossgis24/artwork/003_dataport.png new file mode 100644 index 0000000..4d1db84 Binary files /dev/null and b/fossgis24/artwork/003_dataport.png differ diff --git a/fossgis24/artwork/FOSSGIS-Pause_DaVinci.drp b/fossgis24/artwork/FOSSGIS-Pause_DaVinci.drp new file mode 100644 index 0000000..cd1bac3 Binary files /dev/null and b/fossgis24/artwork/FOSSGIS-Pause_DaVinci.drp differ diff --git a/fossgis24/artwork/LOGO_FOSSGIS24_RGB_300dpi.png b/fossgis24/artwork/LOGO_FOSSGIS24_RGB_300dpi.png new file mode 100644 index 0000000..eb28485 Binary files /dev/null and b/fossgis24/artwork/LOGO_FOSSGIS24_RGB_300dpi.png differ diff --git a/fossgis24/artwork/Titelbild_FOSSGIS2024.png b/fossgis24/artwork/Titelbild_FOSSGIS2024.png new file mode 100644 index 0000000..dba787d Binary files /dev/null and b/fossgis24/artwork/Titelbild_FOSSGIS2024.png differ diff --git a/fossgis24/artwork/Titelbild_FOSSGIS2024_fullhd.png b/fossgis24/artwork/Titelbild_FOSSGIS2024_fullhd.png new file mode 100644 index 0000000..1f7f2a9 Binary files /dev/null and b/fossgis24/artwork/Titelbild_FOSSGIS2024_fullhd.png differ diff --git a/fossgis24/artwork/by-sa.svg b/fossgis24/artwork/by-sa.svg new file mode 100755 index 0000000..f850297 --- /dev/null +++ b/fossgis24/artwork/by-sa.svg @@ -0,0 +1,199 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fossgis24/artwork/fossgis24_logo.png b/fossgis24/artwork/fossgis24_logo.png new file mode 100644 index 0000000..485560f Binary files /dev/null and b/fossgis24/artwork/fossgis24_logo.png differ diff --git a/fossgis24/artwork/intro.svg b/fossgis24/artwork/intro.svg new file mode 100755 index 0000000..8010d23 --- /dev/null +++ b/fossgis24/artwork/intro.svg @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + $url + + + + Hamburg20. - 23. März 2024 + FOSSGIS Konferenz2024 + + + $personnames$title Photo: Hauke Stieler + + diff --git a/fossgis24/artwork/outro.svg b/fossgis24/artwork/outro.svg new file mode 100755 index 0000000..924919f --- /dev/null +++ b/fossgis24/artwork/outro.svg @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + Hamburg20. - 23. März 2024 + FOSSGIS Konferenz2024www.fossgis-konferenz.de/2024 + + + + + + + + + + + + + + + + + + + BY 4.0 DE + https://creativecommons.org/licenses/by/4.0/ + + Photo: Hauke Stieler + + diff --git a/fossgis24/artwork/pause.svg b/fossgis24/artwork/pause.svg new file mode 100755 index 0000000..55e7c7d --- /dev/null +++ b/fossgis24/artwork/pause.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + Photo: Hauke Stieler + + + Hamburg20. - 23. März 2024 + FOSSGIS Konferenz2024www.fossgis-konferenz.de/2024 + + + Gleich geht's weiter... + + + + diff --git a/god2024/__init__.py b/god2024/__init__.py deleted file mode 100644 index c04c938..0000000 --- a/god2024/__init__.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/python3 - -from renderlib import * -from schedulelib import * -from easing import * - -# URL to Schedule-XML -scheduleUrl = 'https://import.c3voc.de/schedule/god2024.xml?showall=yes' - -titlemap = { - -} - -def introFrames(p): - givenFrame = 0 - - nr = p['$id']; - - # 1 Sekunden nix - frames = 1*fps - for i in range(0, frames): - givenFrame += 1 - yield ( - ('bg', 'attr', '{http://www.w3.org/1999/xlink}href', "given-frames/frame%04d.png" % (givenFrame)), - ('layer1', 'style', 'opacity', "%.4f" % 0), # nix - # ('text', 'attr', 'transform', 'translate(%.4f, 0)' % easeOutQuad(i, move, -move, frames)), - ) - - # 1 Sekunde Text Fadein - frames = 1*fps - for i in range(0, frames): - givenFrame += 1 - yield ( - ('bg', 'attr', '{http://www.w3.org/1999/xlink}href', "given-frames/frame%04d.png" % (givenFrame)), - ('layer1', 'style', 'opacity', "%.4f" % easeLinear(i, 0, 1, frames)), - # ('text', 'attr', 'transform', 'translate(%.4f, 0)' % easeOutQuad(i, move, -move, frames)), - ) - - # 5 Sekunden Text - frames = 5*fps - for i in range(0, frames): - givenFrame += 1 - yield ( - ('bg', 'attr', '{http://www.w3.org/1999/xlink}href', "given-frames/frame%04d.png" % (givenFrame)), - ('layer1', 'style', 'opacity', "%.4f" %1), - # ('text', 'attr', 'transform', 'translate(%.4f, 0)' % easeOutQuad(i, move, -move, frames)), - ) - -def outroFrames(p): - xml = etree.parse('god2024/artwork/outro.svg').getroot() - - frames = int(5*fps) - for i in range(0, frames): - yield () - -def pauseFrames(p): - # 1 sekunden fade in - frames = 1*fps - for i in range(0, frames): - yield ( - ('text1', 'style', 'opacity', "%.4f" % easeLinear(i, 0, 1, frames)), - ) - - # 1 sekunde sehen - for i in range(0, frames): - yield ( - ('text1', 'style', 'opacity', "%.4f" % 1), - ) - - # 1 sekunde fadeout - for i in range(0, frames): - yield ( - ('text1', 'style', 'opacity', "%.4f" % easeLinear(i, 1, -1, frames)), - ) - - # 1 sekunde bild - for i in range(0, frames): - yield ( - ('text1', 'style', 'opacity', "%.4f" % 0), - ) - -def debug(): - render( - 'intro.svg', - '../intro.ts', - introFrames, - { - '$id': 65, - '$title': 'OWASP Juice Shop 10th anniversary: Is it still fresh?'.upper(), - '$subtitle': '', - '$personnames': 'Jannik Hollenbach'.upper(), - #'only_render_frame': 353 - 'only_rerender_frames_after': 225 - } - ) - - # render( - # 'pause.svg', - # '../pause.ts', - # pauseFrames - # ) - -def tasks(queue, args, id_list, skip_list): - if not 'outro' in skip_list: - # place a task for the outro into the queue - queue.put(Rendertask( - infile = 'outro.svg', - outfile = 'outro.ts', - sequence = outroFrames - )) - - if not 'pause' in skip_list: - # place the pause-sequence into the queue - queue.put(Rendertask( - infile = 'pause.svg', - outfile = 'pause.ts', - sequence = pauseFrames - )) - - # iterate over all events extracted from the schedule xml-export - for event in events(scheduleUrl, titlemap): - - # skip events which will not be recorded - if event['room'] not in ('Da Capo',) or event['track'] == 'Nomnom': - print("skipping room %s (%s [%s])" % (event['room'], event['title'], event['id'])) - continue - - # when id_list is not empty, only render events which are in id_list - if id_list and int(event['id']) not in id_list: - print("skipping id (%s [%s])" % (event['title'], event['id'])) - continue - - # generate a task description and put them into the queue - queue.put(Rendertask( - infile = 'intro.svg', - outfile = str(event['id']) + ".ts", - sequence = introFrames, - parameters = { - '$id': event['id'], - '$title': event['title'].upper(), - '$subtitle': event['subtitle'], - '$personnames': event['personnames'].upper(), - } - )) - diff --git a/god2024/artwork/intro.svg b/god2024/artwork/intro.svg deleted file mode 100644 index 4a79ff0..0000000 --- a/god2024/artwork/intro.svg +++ /dev/null @@ -1,101 +0,0 @@ - - - -image/svg+xml$title$personnames diff --git a/god2024/artwork/outro.svg b/god2024/artwork/outro.svg deleted file mode 100644 index 8097ff8..0000000 --- a/god2024/artwork/outro.svg +++ /dev/null @@ -1,131 +0,0 @@ - - - -image/svg+xmlCreative Commons Attribution 4.0 International http://creativecommons.org/licenses/by/4.0/ diff --git a/god2024/artwork/pause.svg b/god2024/artwork/pause.svg deleted file mode 100644 index 6dd0f4e..0000000 --- a/god2024/artwork/pause.svg +++ /dev/null @@ -1,78 +0,0 @@ - - - -image/svg+xmlPAUSE diff --git a/jh19-berlin/config.ini b/jh19-berlin/config.ini index 73365a8..6a8740d 100644 --- a/jh19-berlin/config.ini +++ b/jh19-berlin/config.ini @@ -1,13 +1,15 @@ -[meta] +[default] schedule = https://projects.alpaka.space/media/jhber19-schedule.xml template = intro-alpha.mov alpha = true prores = true -inout_type = n +fontfile = true +inout = n [title] in = 175 out = 260 +fontfamily = fontfile = SourceSansPro-Bold.otf fontsize = 90 fontcolor = #ffffff @@ -17,6 +19,7 @@ y = 450 [speaker] in = 175 out = 260 +fontfamily = fontfile = SourceSansPro-Regular.otf fontsize = 36 fontcolor = #ffffff @@ -26,10 +29,11 @@ y = 900 [text] in = 200 out = 250 +fontfamily = fontfile = SourceSansPro-Regular.otf fontsize = 45 fontcolor = #c68100 x = (w-text_w)/2 y = 927 -; text = +text = '' diff --git a/jh20-jue/config.ini b/jh20-jue/config.ini index 05878cf..163ca71 100644 --- a/jh20-jue/config.ini +++ b/jh20-jue/config.ini @@ -1,35 +1,37 @@ -[meta] +[default] schedule = https://releasing.c3voc.de/releases/jhjue2020/schedule-jhjue20.xml ; intro_template.ts was derived from a png which was derived from jhjue-20-intro.svg ; ffmpeg -loop 1 -i jh20-jue/jh20-jue-intro.png -ar 48000 -ac 2 -f s16le -i /dev/zero -c:v mpeg2video -pix_fmt:v yuv420p -qscale:v 2 -qmin:v 2 -qmax:v 7 -keyint_min 0 -bf 0 -g 0 -intra:0 -maxrate:0 90M -c:a mp2 -b:a 384k -t 5 jh20-jue/jh20-jue_intro_template.ts template = jh20-jue_intro_template.ts alpha = false prores = false +; enable using a font file +fontfile = true ; in and out time format: t for seconds, n for frame number inout = n -;; Some font settings can have defaults, which can be overridden in the -;; 'title', 'speaker' and 'text' sections below. -[default] -;; default font -fontfile = ebisu.ttf -;; default font color -fontcolor = #ffffff - -;; fields for title and speaker names are empty in the template.ts, so we'll render them in via ffmpeg +; fields for title and speaker names are empty in the template.ts, so we'll render them in via ffmpeg [title] ; inframe for title in = 20 -; outframe for title +; outframe for totle out = 225 +; title font (either font family or file, see default setting above) +fontfamily = +fontfile = ebisu.ttf ; title font size fontsize = 70 +; title color +fontcolor = #ffffff +; title position from upper left corner x = 600 y = 865 [speaker] in = 40 out = 225 +fontfamily = +fontfile = ebisu.ttf fontsize = 40 fontcolor = #eeeeee x = 600 @@ -39,10 +41,13 @@ y = 950 [text] in = 3 out = 4 +fontfamily = +fontfile = ebisu.ttf fontsize = 45 +fontcolor = #ffffff x = 640 y = 1000 -; text = +text = '' ; build intros via diff --git a/jh21-rn/config.ini b/jh21-rn/config.ini index 626db55..a7870db 100644 --- a/jh21-rn/config.ini +++ b/jh21-rn/config.ini @@ -1,13 +1,15 @@ -[meta] +[default] schedule = https://pretalx.c3voc.de/jugend-hackt-rhein-neckar-2021/schedule/export/schedule.xml template = jh21-rn-template.ts alpha = false prores = false -inout_type = t +fontfile = true +inout = t [title] in = 1 out = 6.5 +fontfamily = fontfile = SourceSansPro-Semibold.ttf fontsize = 85 fontcolor = #ffffff @@ -17,6 +19,7 @@ y = 877 [speaker] in = 2 out = 6.5 +fontfamily = fontfile = SourceSansPro-Semibold.ttf fontsize = 45 fontcolor = #ffffff @@ -26,10 +29,11 @@ y = 954 [text] in = 3 out = 6.5 +fontfamily = fontfile = SourceSansPro-Semibold.ttf fontsize = 45 fontcolor = #ffffff x = 1920 y = 1080 -; text = +text = '' diff --git a/jugendhackt/config.ini b/jugendhackt/config.ini index 94180a5..2400940 100644 --- a/jugendhackt/config.ini +++ b/jugendhackt/config.ini @@ -1,52 +1,39 @@ -[meta] -schedule = https://pretalx.c3voc.de/jhhh23/schedule/export/schedule.xml -;; path to background video -template = intro-background.ts -;; whether background video uses transparency (needs to be .mov) -alpha = false -;; whether background video is prores 4444 -prores = false -;; in and out time format: t for seconds, n for frame number -inout_type = t -;; fade-in duration (seconds), leave out or set to zero to disable -;fade_duration = 0.5 - -;; Some font settings can have defaults, which can be overridden in the -;; 'title', 'speaker' and 'text' sections below. [default] -;; default font -fontfile = SourceSansPro-Semibold.ttf -;; default font color -fontcolor = #ffffff +schedule = https://pretalx.c3voc.de/jhhh23/schedule/export/schedule.xml +template = intro-background.ts +alpha = false +prores = false +fontfile = true +inout = t -;; fields for title and speaker names are empty in the template.ts, so we'll render them in via ffmpeg -;; parameters are: -;; - in: start frame/ time -;; - out: end frame/ time -;; - fontfile: font file -;; - fontcolor: font color -;; - fontsize: font size (pixel) -;; - x: horizontal position (top left corner) -;; - y: vertical position (top left corner) [title] in = 1 out = 9.5 +fontfamily = +fontfile = SourceSansPro-Semibold.ttf fontsize = 67 +fontcolor = #ffffff x = 400 y = 870 [speaker] in = 2 out = 9 +fontfamily = +fontfile = SourceSansPro-Semibold.ttf fontsize = 50 +fontcolor = #ffffff x = 400 y = 950 -;; optional extra text, comment out "text" field to disable + [text] in = 0 out = 0 +fontfamily = +fontfile = SourceSansPro-Semibold.ttf fontsize = 0 +fontcolor = #ffffff x = 0 y = 0 -;text = some additional text +text = '' diff --git a/jugendhackt/source.xcf b/jugendhackt/source.xcf deleted file mode 100644 index e01939d..0000000 Binary files a/jugendhackt/source.xcf and /dev/null differ diff --git a/make-apple-motion.py b/make-apple-motion.py index c2940e9..1d4c631 100755 --- a/make-apple-motion.py +++ b/make-apple-motion.py @@ -11,6 +11,8 @@ import sys import os import re +from xml.sax.saxutils import escape as xmlescape + # Parse arguments parser = argparse.ArgumentParser( description='C3VOC Intro-Outro-Generator - Variant to use with apple Motion Files', @@ -55,20 +57,6 @@ parser.add_argument('--num-audio-streams', dest='naudio', type=int, default=1, h number of audio-streams to generate. defaults to 1 ''') -parser.add_argument('--no-cleanup', action='store_true', help=''' - keep temp-dir for debugging purposes - ''') - -parser.add_argument('--snapshot-sec', type=int, default=3, help=''' - number of seconds into the final clip when to take a snapshot (for inspection purposes or as thumbnail) - ''') - -parser.add_argument('--setting-path', default='hd1080p.compressorsetting', help=''' - filename in the script-dir (where this python script resides), - the work-dir (where the .motn-file resides) or absolute path to - a .compressorsetting file - ''') - args = parser.parse_args() @@ -113,22 +101,9 @@ def describe_event(event): def event_print(event, message): print("{} – {}".format(describe_event(event), message)) -def find_settingpath(): - artwork_dir = os.path.dirname(args.motn) - setting_path = os.path.join(artwork_dir, args.setting_path) - if os.path.exists(setting_path): - return setting_path - - setting_path = os.path.join(os.path.dirname(__file__), args.setting_path) - if os.path.exists(setting_path): - return setting_path - - return args.setting_path - tempdir = tempfile.TemporaryDirectory() print('working in ' + tempdir.name) -settingpath = find_settingpath() def fmt_command(command, **kwargs): @@ -153,13 +128,6 @@ def run_output(command, **kwargs): os.system(f'{cmd} >{t.name} 2>&1') return t.read().decode('utf-8') -def xmlescape(xml): - xml = xml.replace("&", "&") - xml = xml.replace("<", "<") - xml = xml.replace(">", ">") - xml = xml.replace("\"", """) - xml = xml.replace("'", "'") - return xml def enqueue_job(event): event_id = str(event['id']) @@ -176,11 +144,10 @@ def enqueue_job(event): fp.write(xmlstr) compressor_info = run_output( - '/Applications/Compressor.app/Contents/MacOS/Compressor -batchname {batchname} -jobpath {jobpath} -settingpath {settingpath} -locationpath {locationpath}', + '/Applications/Compressor.app/Contents/MacOS/Compressor -batchname {batchname} -jobpath {jobpath} -settingpath hd1080p.compressorsetting -locationpath {locationpath}', batchname=describe_event(event), jobpath=work_doc, - locationpath=intermediate_clip, - settingpath=settingpath) + locationpath=intermediate_clip) match = re.search(r"", compressor_info) if not match: @@ -235,7 +202,6 @@ def finalize_job(job_id, event): intermediate_clip = os.path.join(tempdir.name, event_id + '.mov') final_clip = os.path.join(os.path.dirname(args.motn), event_id + '.ts') copy_clip = os.path.join(os.path.dirname(args.motn), event_id + '.mov') - snapshot_file = os.path.join(os.path.dirname(args.motn), event_id + '.png') shutil.copy(intermediate_clip, copy_clip) @@ -245,11 +211,6 @@ def finalize_job(job_id, event): vcodec=args.vcodec, acodec=args.acodec) - run('ffmpeg -y -hide_banner -loglevel error -i {input} -ss {snapshot_sec} -frames:v 1 -vf scale="iw*sar:ih" -f image2 -y -c png {output}', - input=intermediate_clip, - output=snapshot_file, - snapshot_sec=str(args.snapshot_sec)) - event_print(event, "finalized intro to " + final_clip) @@ -289,9 +250,5 @@ while len(active_jobs) > 0: finalize_job(job_id, event) -if args.no_cleanup: - print('all done, *NOT* cleaning up, *TEMPFILES REMAIN* in ' + tempdir.name) - -else: - print('all done, cleaning up ' + tempdir.name) - tempdir.cleanup() +print('all done, cleaning up ' + tempdir.name) +tempdir.cleanup() diff --git a/make-ffmpeg-fade.py b/make-ffmpeg-fade.py index 3c1eecd..f4e88a3 100755 --- a/make-ffmpeg-fade.py +++ b/make-ffmpeg-fade.py @@ -1,5 +1,329 @@ #!/usr/bin/env python3 # vim: tabstop=4 shiftwidth=4 expandtab -print("ERROR: The functionality of this script was added to 'make-ffmpeg.py'!") -print("Specify meta.fade_duration = 0.5 in the config.ini for the same effect.") +import os +import sys +import subprocess +import renderlib +import argparse +import shlex +from PIL import ImageFont +from configparser import ConfigParser +import json + +# Parse arguments +parser = argparse.ArgumentParser( + description='C3VOC Intro-Outro-Generator - Variant which renders only using video filters in ffmpeg', + usage="./make-ffmpeg.py yourproject/", + formatter_class=argparse.RawTextHelpFormatter) + +parser.add_argument('project', action="store", metavar='Project folder', type=str, help=''' + Path to your project folder + ''') + +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. + 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) + + +cparser = ConfigParser() +cparser.read(os.path.join(os.path.dirname(args.project), 'config.ini')) +template = cparser['default']['template'] +alpha = cparser['default']['alpha'] +prores = cparser['default']['prores'] + +fade_duration = 0.5 + +title_in = float(cparser['title']['in']) +title_out = float(cparser['title']['out']) +title_duration = title_out - title_in +title_font = cparser['title']['font'] +title_fontsize = int(cparser['title']['fontsize']) +title_fontcolor = cparser['title']['fontcolor'] +title_x = int(cparser['title']['x']) +title_y = int(cparser['title']['y']) + +speaker_in = float(cparser['speaker']['in']) +speaker_out = float(cparser['speaker']['out']) +speaker_duration = speaker_out - speaker_in +speaker_font = cparser['speaker']['font'] +speaker_fontsize = int(cparser['speaker']['fontsize']) +speaker_fontcolor = cparser['speaker']['fontcolor'] +speaker_x = int(cparser['speaker']['x']) +speaker_y = int(cparser['speaker']['y']) + +text_in = float(cparser['text']['in']) +text_out = float(cparser['text']['out']) +text_duration = text_out - text_in +text_font = cparser['text']['font'] +text_fontsize = int(cparser['text']['fontsize']) +text_fontcolor = cparser['text']['fontcolor'] +text_x = int(cparser['text']['x']) +text_y = int(cparser['text']['y']) +text_text = cparser['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) + +schedule = cparser['default']['schedule'] + +if not (os.path.exists(os.path.join(args.project, template))): + error("Template file {} in Project Path is missing".format(template)) + +for ffile in (title_font, speaker_font, text_font): + if not (os.path.exists(os.path.join(args.project, ffile))): + error("Font file {} in Project Path is missing".format(ffile)) + +if not (os.path.exists(os.path.join(args.project, 'config.ini'))): + error("config.ini file in Project Path is missing") + +if alpha == 'true' and not fileformat == '.mov': + error("Alpha can only be rendered with .mov source files") + +if not args.project: + error("The Project Path is a required argument") + +if not args.debug and not schedule: + error("Either specify --debug or supply a schedule in config.ini") + +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(schedulelib.events(schedule)) + + +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])) + print("{}, {}".format(w, line)) + if w > (frame_width): + print("too wide, breaking") + 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=title_fontsize-10, encoding="unic") + title = fit_text(string, 1080) + + return title + + +def fit_speaker(string: str): + global translation_font + translation_font = ImageFont.truetype(font_s, size=speaker_fontsize-10, 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']) + event_title = event_title.replace('"', '') + event_title = event_title.replace('\'', '') + event_personnames = event_personnames.replace('"', '') + + t = fit_title(event_title) + s = fit_speaker(event_personnames) + print(s) + + if args.debug: + print('Title: ', t) + print('Speaker: ', s) + + outfile = os.path.join(os.path.dirname(args.project), event_id + '.ts') + + videofilter = "drawtext=fontfile={fontfile}:fontsize={fontsize}:fontcolor={fontcolor}:x={x}:y={y}:text='{text}':".format( + fontfile=font_t, + fontsize=title_fontsize, + fontcolor=title_fontcolor, + x=title_x, + y=title_y, + text=t) + videofilter += "alpha='if(lt(t,{fade_in_start_time}),0,if(lt(t,{fade_in_end_time}),(t-{fade_in_start_time})/{fade_duration},if(lt(t,{fade_out_start_time}),1,if(lt(t,{fade_out_end_time}),({fade_duration}-(t-{fade_out_start_time}))/{fade_duration},0))))',".format( + fade_in_start_time=title_in, + fade_in_end_time=title_in + fade_duration, + fade_out_start_time=title_in + fade_duration + title_duration, + fade_out_end_time=title_in + fade_duration + title_duration + fade_duration, + fade_duration=fade_duration + ) + videofilter += "drawtext=fontfile={fontfile}:fontsize={fontsize}:fontcolor={fontcolor}:x={x}:y={y}:text='{text}':".format( + fontfile=font_s, + fontsize=speaker_fontsize, + fontcolor=speaker_fontcolor, + x=speaker_x, + y=speaker_y, + text=s) + videofilter += "alpha='if(lt(t,{fade_in_start_time}),0,if(lt(t,{fade_in_end_time}),(t-{fade_in_start_time})/{fade_duration},if(lt(t,{fade_out_start_time}),1,if(lt(t,{fade_out_end_time}),({fade_duration}-(t-{fade_out_start_time}))/{fade_duration},0))))',".format( + fade_in_start_time=speaker_in, + fade_in_end_time=speaker_in + fade_duration, + fade_out_start_time=speaker_in + fade_duration + speaker_duration, + fade_out_end_time=speaker_in + fade_duration + speaker_duration + fade_duration, + fade_duration=fade_duration + ) + videofilter += "drawtext=fontfile={fontfile}:fontsize={fontsize}:fontcolor={fontcolor}:x={x}:y={y}:text={text}:".format( + fontfile=font_tt, + fontsize=text_fontsize, + fontcolor=text_fontcolor, + x=text_x, + y=text_y, + text=text_text) + videofilter += "alpha='if(lt(t,{fade_in_start_time}),0,if(lt(t,{fade_in_end_time}),(t-{fade_in_start_time})/{fade_duration},if(lt(t,{fade_out_start_time}),1,if(lt(t,{fade_out_end_time}),({fade_duration}-(t-{fade_out_start_time}))/{fade_duration},0))))'".format( + fade_in_start_time=text_in, + fade_in_end_time=text_in + fade_duration, + fade_out_start_time=text_in + fade_duration + text_duration, + fade_out_end_time=text_in + fade_duration + text_duration + fade_duration, + fade_duration=fade_duration + ) + + if fileformat == '.mov': + if alpha == 'true': + if prores == 'true': + 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}" -shortest -c:v qtrle -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) + else: + cmd = 'ffmpeg -y -i "{0}" -vf "{1}" -map 0:0 -c:v mpeg2video -pix_fmt:v yuv420p -qscale:v 2 -qmin:v 2 -qmax:v 7 -keyint_min 0 -bf 0 -g 0 -maxrate:0 90M -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') diff --git a/make-ffmpeg.py b/make-ffmpeg.py index 94d1514..fe4b745 100755 --- a/make-ffmpeg.py +++ b/make-ffmpeg.py @@ -1,167 +1,155 @@ #!/usr/bin/env python3 # vim: tabstop=4 shiftwidth=4 expandtab -"""See jugendhackt/config.ini for some config file documentation.""" - import os import sys import subprocess -import argparse -import ssl -from configparser import ConfigParser -from pathlib import PurePath -import platform - -from PIL import ImageFont import schedulelib +import argparse +import shlex +from PIL import ImageFont +from configparser import ConfigParser +import json +import platform +import ssl ssl._create_default_https_context = ssl._create_unverified_context -FRAME_WIDTH = 1920 +# Parse arguments +parser = argparse.ArgumentParser( + description='C3VOC Intro-Outro-Generator - Variant which renders only using video filters in ffmpeg', + usage="./make-ffmpeg.py yourproject/", + formatter_class=argparse.RawTextHelpFormatter) + +parser.add_argument('project', action="store", metavar='Project folder', type=str, help=''' + Path to your project folder + ''') + +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. + 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 = [] -class TextConfig: - inpoint: float - outpoint: float - x: int - y: int - - fontfile_path: str - fontsize: int - fontcolor: str - bordercolor: str = None # border is added, if a color is set - - def uses_fontfile(self): - return self.fontfile_path is not None - - def parse(self, cparser_sect, default_fontfile, default_fontcolor): - self.inpoint = cparser_sect.getfloat('in') - self.outpoint = cparser_sect.getfloat('out') - self.x = cparser_sect.getint('x') - self.y = cparser_sect.getint('y') - - self.fontcolor = cparser_sect.get('fontcolor', default_fontcolor) - - fontfile = cparser_sect.get('fontfile', default_fontfile) - self.fontfile_path = str(PurePath(args.project, fontfile).as_posix()) - - if not os.path.exists(self.fontfile_path): - error("Font file {} in Project Path is missing".format(self.fontfile_path)) - - self.fontsize = cparser_sect.getint('fontsize') - self.bordercolor = cparser_sect.get('bordercolor', None) - - def fit_text(self, text: str) -> list[str]: - if not text: - return [""] - - font = ImageFont.truetype( - self.fontfile_path, size=self.fontsize, encoding="unic") - - return fit_text(text, (FRAME_WIDTH-self.x-100), font) - - def get_ffmpeg_filter(self, inout_type: str, fade_time: float, text: list[str]): - if not text: - return "" - - text_duration = self.outpoint - self.inpoint - filter_str = "" - for idx, line in enumerate(text): - filter_str += "drawtext=enable='between({},{},{})':x={}:y={}".format( - inout_type, self.inpoint, self.outpoint, self.x, self.y + (idx*self.fontsize)) - - filter_str += ":fontfile='{}':fontsize={}:fontcolor={}:text={}".format( - self.fontfile_path, self.fontsize, self.fontcolor, ffmpeg_escape_str(line)) - - if self.bordercolor is not None: - filter_str += ":borderw={}:bordercolor={}".format( - self.fontsize / 30, self.bordercolor) - - if fade_time > 0: - filter_str += ":alpha='if(lt(t,{fade_in_start_time}),0,if(lt(t,{fade_in_end_time}),(t-{fade_in_start_time})/{fade_duration},if(lt(t,{fade_out_start_time}),1,if(lt(t,{fade_out_end_time}),({fade_duration}-(t-{fade_out_start_time}))/{fade_duration},0))))'".format( - fade_in_start_time=self.inpoint, - fade_in_end_time=self.inpoint + fade_time, - fade_out_start_time=self.inpoint + fade_time + text_duration, - fade_out_end_time=self.inpoint + fade_time + text_duration + fade_time, - fade_duration=fade_time) - - filter_str += "," - - return filter_str[:-1] - - -class Config: - schedule: str - template_file: str # video background - alpha: bool = False - prores: bool = False - inout_type: str = "t" # in and out time format: t for seconds, n for frame number - fade_duration: float = 0 # fade duration in seconds, 0 to disable - - fileext: str - - title: TextConfig - speaker: TextConfig - text: TextConfig - extra_text: str = "" # additional text - - -def parse_config(filename) -> Config: - if not os.path.exists(filename): - error("config.ini file in Project Path is missing") - - conf = Config() - - cparser = ConfigParser() - cparser.read(filename) - - meta = cparser['meta'] - conf.schedule = meta.get('schedule') - infile = PurePath(args.project, meta.get('template')) - conf.template_file = str(infile) - conf.alpha = meta.getboolean('alpha', conf.alpha) - conf.prores = meta.getboolean('prores', conf.prores) - conf.inout_type = meta.get('inout_type', conf.inout_type) - conf.fade_duration = meta.getfloat('fade_duration', conf.fade_duration) - - defaults = cparser['default'] - default_fontfile = defaults.get('fontfile', None) - default_fontcolor = defaults.get('fontcolor', "#ffffff") - - conf.title = TextConfig() - conf.title.parse(cparser['title'], default_fontfile, default_fontcolor) - conf.speaker = TextConfig() - conf.speaker.parse(cparser['speaker'], default_fontfile, default_fontcolor) - conf.text = TextConfig() - conf.text.parse(cparser['text'], default_fontfile, default_fontcolor) - - conf.extra_text = cparser['text'].get('text', '') - - conf.fileext = infile.suffix - - if not os.path.exists(conf.template_file): - error("Template file {} in Project Path is missing".format(conf.template_file)) - - if conf.alpha and conf.fileext != '.mov': - error("Alpha can only be rendered with .mov source files") - - if not args.project: - error("The Project Path is a required argument") - - if not args.debug and not conf.schedule: - error("Either specify --debug or supply a schedule in config.ini") - - return conf - - -def error(err_str): +def headline(str): print("##################################################") - print(err_str) + print(str) print("##################################################") print() + + +def error(str): + headline(str) parser.print_help() sys.exit(1) +cparser = ConfigParser() +cparser.read(os.path.join(os.path.dirname(args.project), 'config.ini')) +template = cparser['default']['template'] +alpha = cparser['default']['alpha'] +prores = cparser['default']['prores'] +fontfile = cparser['default']['fontfile'] # use a font file instead of a font family +inout = cparser['default']['inout'] # in and out time format: t for seconds, n for frame number + +title_in = cparser['title']['in'] +title_out = cparser['title']['out'] +title_fontfamily = cparser['title']['fontfamily'] +title_fontfile = cparser['title']['fontfile'] +title_fontsize = cparser['title']['fontsize'] +title_fontcolor = cparser['title']['fontcolor'] +title_x = cparser['title']['x'] +title_y = cparser['title']['y'] + +speaker_in = cparser['speaker']['in'] +speaker_out = cparser['speaker']['out'] +speaker_fontfamily = cparser['speaker']['fontfamily'] +speaker_fontfile = cparser['speaker']['fontfile'] +speaker_fontsize = cparser['speaker']['fontsize'] +speaker_fontcolor = cparser['speaker']['fontcolor'] +speaker_x = cparser['speaker']['x'] +speaker_y = cparser['speaker']['y'] + +text_in = cparser['text']['in'] +text_out = cparser['text']['out'] +text_fontfamily = cparser['text']['fontfamily'] +text_fontfile = cparser['text']['fontfile'] +text_fontsize = cparser['text']['fontsize'] +text_fontcolor = cparser['text']['fontcolor'] +text_x = cparser['text']['x'] +text_y = cparser['text']['y'] +text_text = cparser['text']['text'] + +font_t = os.path.join(os.path.dirname(args.project), title_fontfile) +font_s = os.path.join(os.path.dirname(args.project), speaker_fontfile) +font_tt = os.path.join(os.path.dirname(args.project), text_fontfile) + +fileformat = os.path.splitext(template)[1] +infile = os.path.join(os.path.dirname(args.project), template) + +schedule = cparser['default']['schedule'] + +if not (os.path.exists(os.path.join(args.project, template))): + error("Template file {} in Project Path is missing".format(template)) + +for ffile in (title_fontfile, speaker_fontfile, text_fontfile): + if not (os.path.exists(os.path.join(args.project, ffile))): + error("Font file {} in Project Path is missing".format(ffile)) + +if not (os.path.exists(os.path.join(args.project, 'config.ini'))): + error("config.ini file in Project Path is missing") + +if alpha == 'true' and not fileformat == '.mov': + error("Alpha can only be rendered with .mov source files") + +if not args.project: + error("The Project Path is a required argument") + +if not args.debug and not schedule: + error("Either specify --debug or supply a schedule in config.ini") + +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(schedulelib.events(schedule)) + + def describe_event(event): return "#{}: {}".format(event['id'], event['title']) @@ -170,184 +158,164 @@ def event_print(event, message): print("{} – {}".format(describe_event(event), message)) -def fit_text(string: str, max_width: int, font: ImageFont) -> list[str]: - """Break text into array of strings which fit certain a width (in pixels) for the specified font.""" +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 = [] + lines = "" w = 0 - line = [] + line_num = 0 + line = "" for word in split_line: - new_line = line + [word.rstrip(':')] - w = font.getlength(" ".join(new_line)) - if w > max_width: - lines.append(' '.join(line)) - line = [] + left, top, right, bottom = translation_font.getbbox(" ".join([line, word])) + width, height = right - left, bottom - top + if width > (frame_width - (2 * 6)): + lines += line.strip() + "\n" + line = "" - line.append(word.rstrip(':')) - - if word.endswith(':'): - lines.append(' '.join(line)) - line = [] - - if line: - lines.append(' '.join(line)) + line += word + " " + lines += line.strip() return lines -def ffmpeg_escape_str(text: str) -> str: - # Escape according to https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping - # and don't put the string in quotes afterwards! - text = text.replace(",", r"\,") - text = text.replace(':', r"\\:") - text = text.replace("'", r"\\\'") +def fit_title(string: str, fontsize: int, x_offset: int): + global translation_font + translation_font = ImageFont.truetype( + font_t, size=fontsize, encoding="unic") + title = fit_text(string, (1920-x_offset-100)) - return text + return title -def enqueue_job(conf: Config, event): +def fit_speaker(string: str, fontsize: int, x_offset: int): + global translation_font + translation_font = ImageFont.truetype( + font_s, size=fontsize, encoding="unic") + speaker = fit_text(string, (1920-x_offset-100)) + + return speaker + + +def enqueue_job(event): event_id = str(event['id']) - - outfile = str(PurePath(args.project, event_id + '.ts')) - outfile_mov = str(PurePath(args.project, event_id + '.mov')) - if event_id in args.skip: event_print(event, "skipping " + str(event['id'])) return - if (os.path.exists(outfile) or os.path.exists(outfile_mov)) and not args.force: + 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']) + event_title = event_title.replace('"', '\\"') + event_title = event_title.replace('\'', '') + event_personnames = event_personnames.replace('"', '\\"') - title = conf.title.fit_text(event_title) - speakers = conf.speaker.fit_text(event_personnames) - extra_text = conf.text.fit_text(conf.extra_text) + t = fit_title(event_title, int(title_fontsize), int(title_x)) + t = t.replace(':', "\:") # the ffmpeg command needs colons to be escaped + s = fit_speaker(event_personnames, int(speaker_fontsize), int(speaker_x)) if args.debug: - print('Title: ', title) - print('Speaker: ', speakers) + print('Title: ', t) + print('Speaker: ', s) + + outfile = os.path.join(os.path.dirname(args.project), event_id + '.ts') if platform.system() == 'Windows': ffmpeg_path = './ffmpeg.exe' + font_t_win = "/".join(font_t.split("\\")) + font_s_win = "/".join(font_s.split("\\")) + font_tt_win = "/".join(font_tt.split("\\")) else: ffmpeg_path = 'ffmpeg' - videofilter = conf.title.get_ffmpeg_filter(conf.inout_type, conf.fade_duration, title) + "," - videofilter += conf.speaker.get_ffmpeg_filter(conf.inout_type, - conf.fade_duration, speakers) + "," - videofilter += conf.text.get_ffmpeg_filter(conf.inout_type, conf.fade_duration, extra_text) - - cmd = [ffmpeg_path, '-y', '-i', conf.template_file, '-vf', videofilter] - - if conf.fileext == '.mov' and conf.alpha: - if conf.prores: - cmd += ['-vcodec', 'prores_ks', '-pix_fmt', 'yuva444p10le', '-profile:v', - '4444', '-shortest', '-movflags', 'faststart', '-f', 'mov', outfile_mov] + if fontfile == 'true': + if platform.system() == 'Windows': + videofilter = "drawtext=enable='between({8},{0},{1})':fontfile='{2}':fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}',".format( + title_in, title_out, font_t_win, title_fontsize, title_fontcolor, title_x, title_y, t, inout) + videofilter += "drawtext=enable='between({8},{0},{1})':fontfile='{2}':fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}',".format( + speaker_in, speaker_out, font_s_win, speaker_fontsize, speaker_fontcolor, speaker_x, speaker_y, s, inout) + videofilter += "drawtext=enable='between({8},{0},{1})':fontfile='{2}':fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}'".format( + text_in, text_out, font_tt_win, text_fontsize, text_fontcolor, text_x, text_y, text_text, inout) else: - cmd += ['-shortest', '-c:v', 'qtrle', '-movflags', - 'faststart', '-f', 'mov', outfile_mov] + videofilter = "drawtext=enable='between({8},{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, inout) + videofilter += "drawtext=enable='between({8},{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, inout) + videofilter += "drawtext=enable='between({8},{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, inout) else: - cmd += ['-map', '0:0', '-c:v', 'mpeg2video', '-q:v', '2', '-aspect', '16:9', '-map', - '0:1', '-c:a', 'mp2', '-b:a', '384k', '-shortest', '-f', 'mpegts', outfile] + videofilter = "drawtext=enable='between({8},{0},{1})':font='{2}':fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}',".format( + title_in, title_out, title_fontfamily, title_fontsize, title_fontcolor, title_x, title_y, t, inout) + videofilter += "drawtext=enable='between({8},{0},{1})':font='{2}':fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}',".format( + speaker_in, speaker_out, speaker_fontfamily, speaker_fontsize, speaker_fontcolor, speaker_x, speaker_y, s, inout) + videofilter += "drawtext=enable='between({8},{0},{1})':font='{2}':fontsize={3}:fontcolor={4}:x={5}:y={6}:text='{7}'".format( + text_in, text_out, text_fontfamily, text_fontsize, text_fontcolor, text_x, text_y, text_text, inout) + + if fileformat == '.mov': + if alpha == 'true': + if prores == 'true': + cmd = '{3} -y -i "{0}" -vf "{1}" -vcodec prores_ks -pix_fmt yuva444p10le -profile:v 4444 -shortest -movflags faststart -f mov "{2}"'.format( + infile, videofilter, outfile, ffmpeg_path) + else: + cmd = '{3} -y -i "{0}" -vf "{1}" -shortest -c:v qtrle -movflags faststart -f mov "{2}"'.format( + infile, videofilter, outfile, ffmpeg_path) + else: + cmd = '{3} -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, ffmpeg_path) + else: + cmd = '{3} -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, ffmpeg_path) if args.debug: print(cmd) - subprocess.check_call(cmd, - stderr=subprocess.STDOUT, - stdout=subprocess.DEVNULL - ) + run(cmd) return event_id -if __name__ == "__main__": - # Parse arguments - parser = argparse.ArgumentParser( - description='C3VOC Intro-Outro-Generator - Variant which renders only using video filters in ffmpeg', - usage="./make-ffmpeg.py yourproject/", - formatter_class=argparse.RawTextHelpFormatter) - - parser.add_argument('project', action="store", metavar='Project folder', type=str, help=''' - Path to your project folder - ''') - - 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. - 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 = [] - - config = parse_config(PurePath(args.project, 'config.ini')) - - if args.debug: - persons = ['Thomas Roth', 'Dmitry Nedospasov', 'Josh Datko',] - events = [{ - 'id': 'debug', - 'title': 'wallet.fail and the longest talk title to test if the template is big enough', - 'subtitle': 'Hacking the most popular cryptocurrency hardware wallets', - 'persons': persons, - 'personnames': ', '.join(persons), - 'room': 'Borg', - }] - +if args.ids: + if len(args.ids) == 1: + print("enqueuing {} job".format(len(args.ids))) else: - events = list(schedulelib.events(config.schedule)) - - if args.ids: - if len(args.ids) == 1: - print("enqueuing {} job".format(len(args.ids))) - else: - print("enqueuing {} jobs".format(len(args.ids))) + print("enqueuing {} jobs".format(len(args.ids))) +else: + if len(events) == 1: + print("enqueuing {} job".format(len(events))) else: - if len(events) == 1: - print("enqueuing {} job".format(len(events))) - else: - print("enqueuing {} jobs".format(len(events))) + 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 +for event in events: + if args.ids and event['id'] not in args.ids: + continue - event_print(event, "enqueued as " + str(event['id'])) + if args.rooms and event['room'] not in args.rooms: + print("skipping room %s (%s)" % (event['room'], event['title'])) + continue - job_id = enqueue_job(config, event) - if not job_id: - event_print(event, "job was not enqueued successfully, skipping postprocessing") - continue + event_print(event, "enqueued as " + str(event['id'])) - print('all done') + job_id = enqueue_job(event) + if not job_id: + event_print(event, "job was not enqueued successfully, skipping postprocessing") + continue + + +print('all done') diff --git a/mrmcd2019/config.ini b/mrmcd2019/config.ini index fb74efc..165753c 100755 --- a/mrmcd2019/config.ini +++ b/mrmcd2019/config.ini @@ -1,14 +1,16 @@ -[meta] +[default] #schedule = https://talks.mrmcd.net/2019/schedule/export/schedule.xml schedule = file:///home/thorti/git/c3voc/intro-outro-generator/mrmcd2019/schedule.xml template = mrmcd2019.mov alpha = false prores = false -inout_type = n +fontfile = true +inout = n [title] in = 50 out = 225 +fontfamily = fontfile = Jura-Bold.ttf fontsize = 80 fontcolor = #47acda @@ -18,6 +20,7 @@ y = 540 [speaker] in = 75 out = 225 +fontfamily = fontfile = Jura-Regular.ttf fontsize = 50 fontcolor = #094762 @@ -27,9 +30,10 @@ y = 950 [text] in = 242 out = 324 +fontfamily = fontfile = Jura-Regular.ttf fontsize = 45 fontcolor = #c68100 x = (w-text_w)/2 y = 927 -; text = +text = '' diff --git a/osmodevcon24/__init__.py b/osmodevcon24/__init__.py deleted file mode 100644 index 4264294..0000000 --- a/osmodevcon24/__init__.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/python3 - -from renderlib import * -from schedulelib import * -from easing import * - -# URL to Schedule-XML -scheduleUrl = 'https://pretalx.sysmocom.de/osmodevcon2024/schedule/export/schedule.xml' - -def introFrames(args): - #fade in title - frames = 3*fps - for i in range(0, frames): - yield( - ('title', 'style', 'opacity', easeInQuad(i, 0, 1, frames)), - ) - # fade in subtitle and names - frames = 1*fps - for i in range(0, frames): - yield( - ('title', 'style', 'opacity', 1), - ('subtitle', 'style', 'opacity', easeInQuad(i, 0, 1, frames)), - ('personnames', 'style', 'opacity', easeInQuad(i, 0, 1, frames)), - ) - #show whole image for 2 seconds - frames = 2*fps - for i in range(0, frames): - yield( - ('title', 'style', 'opacity', 1), - ('personnames', 'style', 'opacity', 1), - ('subtitle', 'style', 'opacity', 1), - ) - -def backgroundFrames(parameters): - frames = 5*fps - for i in range(0, frames): - yield( - ('logo', 'style', 'opacity', 1), - ) - -def outroFrames(args): - frames = 2*fps - for i in range(0, frames): - yield( - ('logo', 'style', 'opacity', 1), - ('sublogo', 'style', 'opacity', 1), - ('cclogo', 'style', 'opacity', 1), - ) - # fade out - frames = 3*fps - for i in range(0, frames): - yield( - ('logo', 'style', 'opacity', "%.4f" % easeInCubic(i, 1, -1, frames)), - ('sublogo', 'style', 'opacity', "%.4f" % easeInCubic(i, 1, -1, frames)), - ('cclogo', 'style', 'opacity', "%.4f" % easeInCubic(i, 1, -1, frames)), - ) - -def pauseFrames(args): - #fade in pause - frames = 4*fps - for i in range(0, frames): - yield( - ('pause', 'style', 'opacity', "%.4f" % easeInCubic(i, 0.2, 1, frames)), - ) - - # fade out - frames = 4*fps - for i in range(0, frames): - yield( - ('pause', 'style', 'opacity', "%.4f" % easeInCubic(i, 1, -0.8, frames)), - ) - -def debug(): - render('intro.svg', - '../intro.ts', - introFrames, - { - '$id': 7776, - '$title': 'Configuring + running GPRS/EDGE data services with OsmoPCU, OsmoSGSN and OpenGGSN', - '$subtitle': 'With some subtitle!', - '$personnames': 'Alexander Chemeris + Harald Welte' - } - ) - - render('outro.svg', - '../outro.ts', - outroFrames - ) - - render( - 'background.svg', - '../background.ts', - backgroundFrames - ) - - render('pause.svg', - '../pause.ts', - pauseFrames - ) - - -def tasks(queue, args, idlist, skiplist): - # iterate over all events extracted from the schedule xml-export - for event in events(scheduleUrl): - if not (idlist==[]): - if 000000 in idlist: - print("skipping id (%s [%s])" % (event['title'], event['id'])) - continue - if int(event['id']) not in idlist: - print("skipping id (%s [%s])" % (event['title'], event['id'])) - continue - - # generate a task description and put them into the queue - queue.put(Rendertask( - infile = 'intro.svg', - outfile = str(event['id'])+".ts", - sequence = introFrames, - parameters = { - '$id': event['id'], - '$title': event['title'], - '$subtitle': event['subtitle'], - '$personnames': event['personnames'] - } - )) - - # place a task for the outro into the queue - if not "out" in skiplist: - queue.put(Rendertask( - infile = 'outro.svg', - outfile = 'outro.ts', - sequence = outroFrames - )) - - # place the pause-sequence into the queue - if not "pause" in skiplist: - queue.put(Rendertask( - infile = 'pause.svg', - outfile = 'pause.ts', - sequence = pauseFrames - )) - - # place the background-sequence into the queue - if not "bg" in skiplist: - queue.put(Rendertask( - infile = 'background.svg', - outfile = 'background.ts', - sequence = backgroundFrames - )) diff --git a/osmodevcon24/artwork/background.svg b/osmodevcon24/artwork/background.svg deleted file mode 100644 index 3a87e75..0000000 --- a/osmodevcon24/artwork/background.svg +++ /dev/null @@ -1,946 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - Pause Pause - o - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/osmodevcon24/artwork/intro.svg b/osmodevcon24/artwork/intro.svg deleted file mode 100644 index 2a8b797..0000000 --- a/osmodevcon24/artwork/intro.svg +++ /dev/null @@ -1,1372 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - Pause Pause $title $personnames $subtitle - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/osmodevcon24/artwork/outro.svg b/osmodevcon24/artwork/outro.svg deleted file mode 100644 index 1fbdc0e..0000000 --- a/osmodevcon24/artwork/outro.svg +++ /dev/null @@ -1,1479 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - Pause Pause - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/osmodevcon24/artwork/pause.svg b/osmodevcon24/artwork/pause.svg deleted file mode 100644 index e8d1e7b..0000000 --- a/osmodevcon24/artwork/pause.svg +++ /dev/null @@ -1,1208 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - Pause Pause - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/requirements.txt b/requirements.txt index efa6214..181482a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -pillow>=8.0.0 +pillow cssutils==1.0.2 -lxml~=5.3 -svg.path~=6.0 -Wand~=0.6.5 +lxml==4.9.1 +svg.path==4.0.2 +Wand==0.6.5 diff --git a/schedulelib.py b/schedulelib.py index a986498..340e4d8 100644 --- a/schedulelib.py +++ b/schedulelib.py @@ -100,12 +100,6 @@ def events(scheduleUrl, titlemap={}): url = event.find('url').text.strip() else: url = '' - - if event.find('track') is not None and event.find('track').text is not None: - track = event.find('track').text - else: - track = '' - # yield a tupel with the event-id, event-title and person-names yield { 'day': day.get('index'), @@ -115,7 +109,7 @@ def events(scheduleUrl, titlemap={}): 'persons': personnames, 'personnames': ', '.join(personnames), 'room': room.attrib['name'], - 'track': track, + 'track': event.find('track').text, 'url': url } diff --git a/vcfb24/Computerfont.ttf b/vcfb24/Computerfont.ttf deleted file mode 100644 index 49f1c62..0000000 Binary files a/vcfb24/Computerfont.ttf and /dev/null differ diff --git a/vcfb24/SourceSansPro-Semibold.ttf b/vcfb24/SourceSansPro-Semibold.ttf deleted file mode 100644 index bf69cc2..0000000 Binary files a/vcfb24/SourceSansPro-Semibold.ttf and /dev/null differ diff --git a/vcfb24/config.ini b/vcfb24/config.ini deleted file mode 100644 index 712eff6..0000000 --- a/vcfb24/config.ini +++ /dev/null @@ -1,37 +0,0 @@ -[meta] -schedule = http://vcfb.de/2024/schedule.xml -# ffmpeg -loop 1 -i intro.png -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -c:v libx264 -tune stillimage -pix_fmt yuv420p -c:a aac -r 25 -t 10 intro.mp4 -template = intro.mp4 -alpha = false -prores = false -inout_type = t -fade_duration = 0.5 - -[title] -in = 1 -out = 9.5 -fontfile = Computerfont.ttf -fontsize = 100 -fontcolor = #ffffff -x = 85 -y = 122 - -[speaker] -in = 2 -out = 9 -fontfile = SourceSansPro-Semibold.ttf -fontsize = 65 -fontcolor = #ffffff -x = 85 -y = 861 - - -[text] -in = 0 -out = 0 -fontfile = Computerfont.ttf -fontsize = 0 -fontcolor = #ffffff -x = 0 -y = 0 -; text = diff --git a/vcfb24/intro.svg b/vcfb24/intro.svg deleted file mode 100644 index 30044ab..0000000 --- a/vcfb24/intro.svg +++ /dev/null @@ -1,15584 +0,0 @@ - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -   - $subtitle $title  $personnames - diff --git a/vcfb24/lower-third.svg b/vcfb24/lower-third.svg deleted file mode 100644 index a70d3b4..0000000 --- a/vcfb24/lower-third.svg +++ /dev/null @@ -1,6231 +0,0 @@ - - - -$PERSON diff --git a/vcfb24/outro.svg b/vcfb24/outro.svg deleted file mode 100644 index 45f706d..0000000 --- a/vcfb24/outro.svg +++ /dev/null @@ -1,1668 +0,0 @@ - - - - - - - - - - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  -More Recordings available on media.ccc.de - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/vcfb24/pause.svg b/vcfb24/pause.svg deleted file mode 100644 index 8ef961c..0000000 --- a/vcfb24/pause.svg +++ /dev/null @@ -1,6626 +0,0 @@ - - - -image/svg+xmlPause - \ No newline at end of file