make-ffmpeg: Use fit_text from make-ffmpeg-fade (break into lines array)

This commit is contained in:
Jannik Beyerstedt 2024-11-02 17:21:47 +01:00
parent 6876f2cf44
commit 31f01ca386
10 changed files with 63 additions and 41 deletions

View file

@ -31,5 +31,5 @@ fontsize = 45
fontcolor = #FB48C4 fontcolor = #FB48C4
x = (w-text_w)/2 x = (w-text_w)/2
y = 1000 y = 1000
text = 'Chaos Communication Camp 2023' text = Chaos Communication Camp 2023

View file

@ -31,5 +31,5 @@ fontsize = 45
fontcolor = #c68100 fontcolor = #c68100
x = (w-text_w)/2 x = (w-text_w)/2
y = 927 y = 927
text = 'chaos communication camp 2019' text = chaos communication camp 2019

View file

@ -31,5 +31,5 @@ fontsize = 45
fontcolor = #ffffff fontcolor = #ffffff
x = 640 x = 640
y = 1000 y = 1000
text = '' ; text =

View file

@ -31,5 +31,5 @@ fontsize = 45
fontcolor = #c68100 fontcolor = #c68100
x = (w-text_w)/2 x = (w-text_w)/2
y = 927 y = 927
text = '' ; text =

View file

@ -42,7 +42,7 @@ fontsize = 45
fontcolor = #ffffff fontcolor = #ffffff
x = 640 x = 640
y = 1000 y = 1000
text = '' ; text =
; build intros via ; build intros via

View file

@ -31,5 +31,5 @@ fontsize = 45
fontcolor = #ffffff fontcolor = #ffffff
x = 1920 x = 1920
y = 1080 y = 1080
text = '' ; text =

View file

@ -42,11 +42,11 @@ fontsize = 50
x = 400 x = 400
y = 950 y = 950
;; optional extra text ;; optional extra text, comment out "text" field to disable
[text] [text]
in = 0 in = 0
out = 0 out = 0
fontsize = 0 fontsize = 0
x = 0 x = 0
y = 0 y = 0
text = '' ;text = some additional text

View file

@ -16,6 +16,8 @@ from PIL import ImageFont
import schedulelib import schedulelib
ssl._create_default_https_context = ssl._create_unverified_context ssl._create_default_https_context = ssl._create_unverified_context
FRAME_WIDTH = 1920
class TextConfig: class TextConfig:
inpoint: float inpoint: float
@ -58,17 +60,24 @@ class TextConfig:
self.fontsize = cparser_sect.getint('fontsize') self.fontsize = cparser_sect.getint('fontsize')
self.bordercolor = cparser_sect.get('bordercolor', None) self.bordercolor = cparser_sect.get('bordercolor', None)
def fit_text(self, text: str): def fit_text(self, text: str) -> list[str]:
global translation_font if not text:
translation_font = ImageFont.truetype( return [""]
font = ImageFont.truetype(
self.fontfile_path, size=self.fontsize, encoding="unic") self.fontfile_path, size=self.fontsize, encoding="unic")
# TODO: Make this work with font family as well! # TODO: Make this work with font family as well!
return fit_text(text, (1920-self.x-100)) return fit_text(text, (FRAME_WIDTH-self.x-100), font)
def get_ffmpeg_filter(self, inout_type: str, text: str): def get_ffmpeg_filter(self, inout_type: str, text: list[str]):
filter_str = "drawtext=enable='between({},{},{})'".format( if not text:
return ""
filter_str = ""
for idx, line in enumerate(text):
filter_str += "drawtext=enable='between({},{},{})'".format(
inout_type, self.inpoint, self.outpoint) inout_type, self.inpoint, self.outpoint)
if self.uses_fontfile(): if self.uses_fontfile():
@ -77,12 +86,15 @@ class TextConfig:
filter_str += ":font='{}'".format(self.fontfamily) filter_str += ":font='{}'".format(self.fontfamily)
if self.bordercolor is not None: if self.bordercolor is not None:
filter_str += ":borderw={}:bordercolor={}".format(self.fontsize / 30, self.bordercolor) filter_str += ":borderw={}:bordercolor={}".format(
self.fontsize / 30, self.bordercolor)
filter_str += ":fontsize={0}:fontcolor={1}:x={2}:y={3}:text={4}".format( filter_str += ":fontsize={0}:fontcolor={1}:x={2}:y={3}:text={4}".format(
self.fontsize, self.fontcolor, self.x, self.y, ffmpeg_escape_str(text)) self.fontsize, self.fontcolor, self.x, self.y + (idx*self.fontsize), ffmpeg_escape_str(line))
return filter_str filter_str += ","
return filter_str[:-1]
class Config: class Config:
@ -97,7 +109,7 @@ class Config:
title: TextConfig title: TextConfig
speaker: TextConfig speaker: TextConfig
text: TextConfig text: TextConfig
text_text: str = "" # additional text extra_text: str = "" # additional text
def parse_config(filename) -> Config: def parse_config(filename) -> Config:
@ -129,7 +141,7 @@ def parse_config(filename) -> Config:
conf.text = TextConfig() conf.text = TextConfig()
conf.text.parse(cparser['text'], default_fontfile, default_fontfamily, default_fontcolor) conf.text.parse(cparser['text'], default_fontfile, default_fontfamily, default_fontcolor)
conf.text_text = cparser['text'].get('text', '') conf.extra_text = cparser['text'].get('text', '')
conf.fileext = infile.suffix conf.fileext = infile.suffix
@ -165,20 +177,29 @@ def event_print(event, message):
print("{} {}".format(describe_event(event), message)) print("{} {}".format(describe_event(event), message))
def fit_text(string: str, frame_width): 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."""
split_line = [x.strip() for x in string.split()] split_line = [x.strip() for x in string.split()]
lines = "" lines = []
line = "" w = 0
line = []
for word in split_line: for word in split_line:
left, top, right, bottom = translation_font.getbbox(" ".join([line, word])) new_line = line + [word.rstrip(':')]
width, height = right - left, bottom - top w = font.getlength(" ".join(new_line))
if width > (frame_width - (2 * 6)): if w > max_width:
lines += line.strip() + "\n" lines.append(' '.join(line))
line = "" line = []
line += word + " " line.append(word.rstrip(':'))
if word.endswith(':'):
lines.append(' '.join(line))
line = []
if line:
lines.append(' '.join(line))
lines += line.strip()
return lines return lines
@ -210,6 +231,7 @@ def enqueue_job(conf: Config, event):
title = conf.title.fit_text(event_title) title = conf.title.fit_text(event_title)
speakers = conf.speaker.fit_text(event_personnames) speakers = conf.speaker.fit_text(event_personnames)
extra_text = conf.text.fit_text(conf.extra_text)
if args.debug: if args.debug:
print('Title: ', title) print('Title: ', title)
@ -222,7 +244,7 @@ def enqueue_job(conf: Config, event):
videofilter = conf.title.get_ffmpeg_filter(conf.inout_type, title) + "," videofilter = conf.title.get_ffmpeg_filter(conf.inout_type, title) + ","
videofilter += conf.speaker.get_ffmpeg_filter(conf.inout_type, speakers) + "," videofilter += conf.speaker.get_ffmpeg_filter(conf.inout_type, speakers) + ","
videofilter += conf.text.get_ffmpeg_filter(conf.inout_type, conf.text_text) videofilter += conf.text.get_ffmpeg_filter(conf.inout_type, extra_text)
cmd = [ffmpeg_path, '-y', '-i', conf.template_file, '-vf', videofilter] cmd = [ffmpeg_path, '-y', '-i', conf.template_file, '-vf', videofilter]
@ -298,7 +320,7 @@ if __name__ == "__main__":
persons = ['Thomas Roth', 'Dmitry Nedospasov', 'Josh Datko',] persons = ['Thomas Roth', 'Dmitry Nedospasov', 'Josh Datko',]
events = [{ events = [{
'id': 'debug', 'id': 'debug',
'title': 'wallet.fail', 'title': 'wallet.fail and the longest talk title to test if the template is big enough',
'subtitle': 'Hacking the most popular cryptocurrency hardware wallets', 'subtitle': 'Hacking the most popular cryptocurrency hardware wallets',
'persons': persons, 'persons': persons,
'personnames': ', '.join(persons), 'personnames': ', '.join(persons),

View file

@ -32,4 +32,4 @@ fontsize = 45
fontcolor = #c68100 fontcolor = #c68100
x = (w-text_w)/2 x = (w-text_w)/2
y = 927 y = 927
text = '' ; text =

View file

@ -34,4 +34,4 @@ fontsize = 0
fontcolor = #ffffff fontcolor = #ffffff
x = 0 x = 0
y = 0 y = 0
text = '' ; text =