Last active
May 28, 2020 01:48
Star
You must be signed in to star a gist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import subprocess | |
from PIL import ImageFont | |
import random | |
import os | |
import shutil | |
os.getcwd() | |
def fit_text(string: str, frame_width, translation_font): | |
split_line = [x.strip() for x in string.split()] | |
lines = "" | |
w = 0 | |
line_num = 0 | |
line = "" | |
for word in split_line: | |
# Make a test | |
w, _ = translation_font.getsize(" ".join([line, word])) | |
# If it exceeds the frame width, add a new line | |
if w > (frame_width - (2 * 6)): # Leave 6px margin on each side | |
lines += line.strip() + "\n" | |
line = "" | |
line += word + " " | |
lines += line.strip() # Append leftover words | |
return lines.split("\n") | |
def lines2str(lines_list, duration_per_img, fontsize, fontcolor, fontfamily_path, text_animation): | |
a = "" | |
i = 0 | |
if text_animation[1] == "d": | |
if len(lines_list) > 1: | |
i = -fontsize * (len(lines_list)-1) | |
delta_t = 0 | |
for line in lines_list: | |
if text_animation == "fio": | |
# fade in fade out -- without background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,1),0,if(lt(t,3),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f" :x=(w-text_w)/2:y=((h-text_h)/2)+{i}," | |
elif text_animation == "fiob": | |
## fade in fade out -- with background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,1),0,if(lt(t,3),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + " :shadowcolor=black@0.6:shadowx=0:shadowy=0 :box=1: boxcolor=black@0.4:boxborderw=1" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f" :x=(w-text_w)/2:y=((h-text_h)/2)+{i}," | |
elif text_animation == "ld": | |
# ld -- coming from left -- without background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(-w+((3*w-tw)*t/2.5))\,40) :y=h-80-(text_h)+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "ldb": | |
# ld -- coming from left -- with background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + " :shadowcolor=black@0.6:shadowx=0:shadowy=0 :box=1: boxcolor=black@0.4:boxborderw=1" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(-w+((3*w-tw)*t/2.5))\,40) :y=h-80-(text_h)+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "lu": | |
# lu -- coming from left -- without background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(-w+((3*w-tw)*t/2.5))\,40) :y=40+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "lub": | |
# lu -- coming from left -- with background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + " :shadowcolor=black@0.6:shadowx=0:shadowy=0 :box=1: boxcolor=black@0.4:boxborderw=1" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(-w+((3*w-tw)*t/2.5))\,40) :y=40+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "rd": | |
# rd -- coming from bottom -- without background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(2*w-((3*w+tw)*t/2.6))\,w-40-(text_w)) :y=h-80-(text_h)+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "rdb": | |
# rd -- coming from right -- with background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + " :shadowcolor=black@0.6:shadowx=0:shadowy=0 :box=1: boxcolor=black@0.4:boxborderw=1" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(2*w-((3*w+tw)*t/2.6))\,w-40-(text_w)) :y=h-80-(text_h)+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "ru": | |
# ru -- coming from right -- without background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(2*w-((3*w+tw)*t/2.6))\,w-40-(text_w)) :y=40+(text_h)+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "rub": | |
# ru -- coming from right -- with background | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + " :shadowcolor=black@0.6:shadowx=0:shadowy=0 :box=1: boxcolor=black@0.4:boxborderw=1" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(lt(t\,1)\,(2*w-((3*w+tw)*t/2.6))\,w-40-(text_w)) :y=40+(text_h)+{i}:enable='between(t,0.5,50)'," | |
elif text_animation == "blnk": | |
## blink | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,0),0,if(lt(t,0),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=(w-text_w)/2:y=((h-text_h)/2)+{i}:enable=if(lt(t\,5)\, lt(mod(t\,1)\,0.5)\, lt(mod(t\,1)\,1)) ," | |
elif text_animation == "rand": | |
# random location | |
time_display = (duration_per_img-5) # 5 -- 1 second to load, 2 to fade in and 2 to fade out | |
anim = f":alpha=\'if(lt(t,1),0,if(lt(t,3),(t-1)/2,if(lt(t,3+{time_display}),1,if(lt(t,5+{time_display}),(2-(t-(3+{time_display})))/2,0))))\'" | |
a += f"drawtext=fontsize={fontsize}" + ":fontcolor=" + fontcolor + ":fontfile=" + fontfamily_path + f":text={line}" + anim + f":x=if(eq(mod(t\,2)\,0+{i})\,rand(0\,(w-text_w))\,x):y=if(eq(mod(t\,2)\,0)\,rand(0\,(h-text_h))\,y) ," | |
i += fontsize | |
return a[:-1] | |
def text2str(text_str, duration_per_img, fontsize, fontcolor, fontfamily_path, width, text_animation): | |
translation_font = ImageFont.truetype(fontfamily_path, size=fontsize, encoding="unic") | |
list_ = fit_text(text_str, width, translation_font) | |
str_list = lines2str(list_, duration_per_img, fontsize, fontcolor, fontfamily_path, text_animation) | |
return str_list | |
def single_img(img_path, text_str, duration_per_img, fontsize, fontcolor, fontfamily_path, text_animation, img_num): | |
img_num = str(img_num) | |
cmd1 = f"ffmpeg -y -hide_banner -loglevel panic -r 1/{duration_per_img} -i {img_path} -c:v libx264 -vf fps=25 -pix_fmt yuv420p ./temp/vid{img_num}_1.mp4" | |
subprocess.run(cmd1, shell=True) | |
x_loc_list = [1280, 1280, 1280/2, 0, 0, 1280/2, 1280/2, 0, 1280/2] | |
y_loc_list = [720, 0, 720/2, 720, 0, 0, 720, 720/2, 720/2] | |
loc = random.randint(0,len(x_loc_list)-1) | |
pan_list = [100, 200, 300, 400, 500, 600] | |
pan = random.randint(0,len(pan_list)-1) | |
pan = pan_list[pan] | |
zoom = f"zoompan=z='1+(0.1*in/{pan})':x='{x_loc_list[loc]}*in/{pan}':y='{y_loc_list[loc]}*in/{pan}'" | |
cmd2 = f"ffmpeg -y -hide_banner -loglevel panic -i ./temp/vid{img_num}_1.mp4 -vf \"scale=8000:-1,{zoom}:d=1\" ./temp/vid{img_num}_2.mp4" | |
subprocess.run(cmd2, shell=True) | |
if text_animation[0] == "f": | |
resolution = 1280/1.1 | |
else: | |
resolution = 1280/1.7 | |
text_data = text2str(text_str, duration_per_img, fontsize, fontcolor, fontfamily_path, resolution, text_animation) | |
cmd3 = f"ffmpeg -y -hide_banner -loglevel panic -i ./temp/vid{img_num}_2.mp4 -filter_complex \"[0:v]{text_data}\" ./temp2/vid{img_num}_3.mp4" | |
subprocess.run(cmd3, shell=True) | |
def merge(vid_folder_path): | |
cmd6 = "ffmpeg -y -hide_banner -loglevel panic -i \"concat:" | |
files_len = len(os.listdir(vid_folder_path)) | |
for i,filename in enumerate(os.listdir(vid_folder_path)): | |
cmd = f"ffmpeg -y -hide_banner -loglevel panic -i {vid_folder_path}/vid{i}_3.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts {vid_folder_path}/intermediate{i}.ts" | |
subprocess.run(cmd, shell=True) | |
cmd6 += f"{vid_folder_path}/intermediate{i}.ts" + "|" | |
cmd6 = cmd6[:-1] + "\" -c copy -bsf:a aac_adtstoasc output.mp4" | |
subprocess.run(cmd6, shell=True) | |
def list_from_text_file(text_file_path): | |
pass # return text_list | |
def main(img_folder, text_file_path, duration_per_img, fontsize, fontcolor, fontfamily_path, audio_file_path=None, text_animation_list=None, debug=False): | |
# text_list = list_from_text_file(text_file_path) | |
text_list = ["The Irene Method is one effective way to treat visual perception disorders.0", # replace this with file | |
"The Irene Method is one effective way to treat visual perception disorders.1", | |
"The Irene Method is one effective way to treat visual perception disorders.2", | |
"The Irene Method is one effective way to treat visual perception disorders.3", | |
"The Irene Method is one effective way to treat visual perception disorders.4", | |
"The Irene Method is one effective way to treat visual perception disorders.5", | |
"The Irene Method is one effective way to treat visual perception disorders.6", | |
"The Irene Method is one effective way to treat visual perception disorders.7", | |
"The Irene Method is one effective way to treat visual perception disorders.8", | |
] | |
assert len(text_list) >= len(os.listdir(img_folder)), "make sure each image have its own text defined" | |
txt_anim_list = ['fio', 'fiob', 'ld', 'ldb', 'lu', 'lub', 'rd', 'rdb', 'ru', 'rub', 'blnk'] #, 'rand'] | |
if not os.path.exists("./temp"): | |
os.mkdir("./temp") | |
if not os.path.exists("./temp2"): | |
os.mkdir("./temp2") | |
if not os.path.exists("./output"): | |
os.mkdir("./output") | |
for i, filename in enumerate(os.listdir(img_folder)): | |
if debug: # only run once | |
text_animation = "fio" | |
img_path = img_folder + "/" + filename | |
single_img(img_path, text_list[i], duration_per_img, fontsize, fontcolor, fontfamily_path, text_animation, i) | |
break | |
if text_animation_list == None: # choose random animation if not provided | |
if i == 0: # first img | |
text_animation = txt_anim_list[0] | |
elif i == len(os.listdir(img_folder))-1: # last image | |
text_animation = txt_anim_list[1] | |
else: | |
val = random.randint(0,len(txt_anim_list)-1) | |
text_animation = txt_anim_list[val] | |
img_path = img_folder + "/" + filename | |
single_img(img_path, text_list[i], duration_per_img, fontsize, fontcolor, fontfamily_path, text_animation, i) | |
else: # if custom defined animation | |
assert len(text_animation_list) == len(os.listdir(img_folder)), "make sure each image have its own animation defined" | |
img_path = img_folder + "/" + filename | |
single_img(img_path, text_list[i], duration_per_img, fontsize, fontcolor, fontfamily_path, text_animation_list[i], i) | |
merge("./temp2") | |
if audio_file_path: | |
cmd = f"ffmpeg -y -hide_banner -loglevel panic -i output.mp4 -i {audio_file_path} -c:v copy -c:a aac -shortest ./output/output.mp4" | |
subprocess.run(cmd, shell=True) | |
os.remove("output.mp4") | |
shutil.rmtree("./temp") | |
shutil.rmtree("./temp2") | |
if __name__ == "__main__": | |
img_folder_path = "./images" | |
text_file_path = None # -------------- | |
fontfamily_path = "EmpireStateNF.ttf" | |
audio_file_path = "audio_file.mpeg" | |
fontsize = 30 | |
fontcolor = "white" | |
duration_per_img = 10 | |
text_animation_list = ['fio']*9 | |
main(img_folder_path, text_file_path, duration_per_img, fontsize, fontcolor, fontfamily_path, audio_file_path, text_animation_list=None, debug=False) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment