77import os .path
88import shutil
99
10- logger = logging .getLogger (" __name__" )
10+ logger = logging .getLogger (__name__ )
1111
1212logger .setLevel (logging .DEBUG )
1313
@@ -48,19 +48,49 @@ def timecode_to_timestamp(timecode, framerate = FRAMERATE):
4848
4949def form_video (video , talk , start_tc , end_tc , framerate = FRAMERATE , out_dir = OUT_DIR , temp_dir = TEMP_DIR ):
5050
51- end_dur = 10
52- end_fade = 2
53-
51+ # Timing information
52+ end_dur = 10 # How long to hold the endslate for
53+ end_fade_in = 2
54+ end_fade = 2 # Fade out time on endslate
55+ afade_in = 3 # Talk audio fade in duration
56+ afade_out = 3 # Talk audio fade out duration
57+ spn_dur = 5 # Sponsor slide hold duration
58+ spn_fade_in = 0.4 # Sponsor slide fade-in duration
59+ spn_fade_out = 0.4 # Sponsor slide cross-fade out duration
60+ title_dur = 5 # How long to hold the title card
61+ title_fade_out = 0.4 # Title card into program duration
62+ main_fade_out = 0.4 # Main program into endboard duration
63+
64+ # Colour and design information
65+ col_talk = "#f9e200"
66+ col_pres = "#2eadd9"
67+ col_bkg = "#00000000" #"#21301850"
68+
69+ # Resource files
70+ bkgd_file = "resources/BG_V3.mp4"
71+ transp_file = "resources/transparent.png"
72+ logo_file = "resources/logo.svg"
73+ spons_file = "resources/sponsor_slide_rounded.png"
74+
75+ # Generated files paths
76+ copr_file = "temp/copyright.png"
77+ spres_file = "temp/start_pres.png"
78+ stalk_file = "temp/start_title.png"
79+
80+ # Convert start and end to some other forms
5481 start_s = timecode_to_seconds (start_tc )
5582 end_s = timecode_to_seconds (end_tc )
56-
5783 start_ts = timecode_to_timestamp (start_tc )
5884 end_ts = timecode_to_timestamp (end_tc )
5985
60- fade_offset = end_s - start_s - 1
86+ # Calculate some other reused variables
87+ fade_offset = end_s - start_s - (end_fade_in / 2. )
88+ afade_offset = fade_offset - (afade_out / 2. ) # When to fade out the main talk audio
89+ eb_end = fade_offset + end_dur #
90+ end_tdur = end_dur + end_fade # Total duration of the endslate, including fade time
91+ title_end = spn_dur + title_dur
6192
62- start_png = Path .joinpath (Path (temp_dir ), Path ("start.png" ))
63- end_png = Path .joinpath (Path (temp_dir ), Path ("end.png" ))
93+ lic_text = "This work is licensed under CC BY-SA 4.0. To view a copy of this license, visit https://creativecommons.org/licenses/by-sa/4.0/"
6494
6595 start_timestamp = datetime .datetime .now ().strftime ("%Y%m%d-%H%M%S" )
6696
@@ -74,72 +104,47 @@ def form_video(video, talk, start_tc, end_tc, framerate = FRAMERATE, out_dir = O
74104 output_path = Path .joinpath (Path (out_dir ), output_file )
75105 log_path = Path .joinpath (Path (LOG_DIR ), log_file )
76106
77- start_lt_args = [
78- IMAGEMAGICK_BIN ,
79- "-size" , "1920x1080" , "xc:none" ,
80- "-fill" , "blue" , "-pointsize" , "72" , "-annotate" , "+70+70" , talk ["title" ],
81- "-fill" , "green" , "-pointsize" , "60" , "-annotate" , "+70+200" , talk ["presenter" ],
82- start_png
83- ]
84-
85- start_lt_title_args = [
107+ start_title_args = [
86108 IMAGEMAGICK_BIN ,
87- "-size" , "1860x160 " , "-background" , "#00000000" ,
88- "-fill" , "#f9e200" , "-gravity" , "southwest " , "caption:{}" .format (talk ["title" ]),
89- "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , "#21301850" , "-layers" , "merge" , "+repage" ,
90- "temp/start_title.png"
109+ "-size" , "1800x300 " , "-background" , "#00000000" ,
110+ "-fill" , col_talk , "-gravity" , "center " , "caption:{}" .format (talk ["title" ]),
111+ "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , col_bkg , "-layers" , "merge" , "+repage" ,
112+ stalk_file
91113 ]
92114
93- start_lt_pres_arg = [
115+ start_pres_arg = [
94116 IMAGEMAGICK_BIN ,
95- "-size" , "1860x80" , "-background" , "#00000000" ,
96- "-fill" , "#2eadd9" , "-gravity" , "southwest" , "caption:{}" .format (talk ["presenter" ]),
97- "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , "#21301850" , "-layers" , "merge" , "+repage" ,
98- "temp/start_pres.png"
99- ]
100-
101- end_slide_args = [
102- IMAGEMAGICK_BIN ,
103- "-size" , "1920x1080" , "xc:orange" ,
104- "-fill" , "blue" , "-gravity" , "center" , "-pointsize" , "72" , "-annotate" , "+0-70" , talk ["title" ],
105- "-fill" , "green" , "-gravity" , "center" , "-pointsize" , "60" , "-annotate" , "+0+70" , talk ["presenter" ],
106- end_png
107- ]
108-
109- end_slide_title_args = [
110- IMAGEMAGICK_BIN ,
111- "-size" , "1320x350" , "-background" , "#00000000" ,
112- "-fill" , "#f9e200" , "-gravity" , "Center" , "caption:{}" .format (talk ["title" ]),
113- "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , "#21301850" , "-layers" , "merge" , "+repage" ,
114- "temp/end_title.png"
117+ "-size" , "1800x256" , "-background" , "#00000000" ,
118+ "-fill" , col_pres , "-gravity" , "center" , "caption:{}" .format (talk ["presenter" ]),
119+ "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , col_bkg , "-layers" , "merge" , "+repage" ,
120+ spres_file
115121 ]
116122
117- end_slide_pres_arg = [
123+ copyright_args = [
118124 IMAGEMAGICK_BIN ,
119- "-size" , "700x200 " , "-background" , "#00000000" ,
120- "-fill" , "#2eadd9" , "-gravity" , "Center " , "caption:{}" .format (talk [ "presenter" ] ),
121- "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , "#21301850" , "-layers" , "merge" , "+repage" ,
122- "temp/end_pres.png"
125+ "-size" , "1000x256 " , "-background" , "#00000000" ,
126+ "-fill" , "#2eadd9" , "-gravity" , "east " , "caption:{}" .format (lic_text ),
127+ "(" , "+clone" , "-shadow" , "500x2+0+0" , ")" , "+swap" , "-background" , col_bkg , "-layers" , "merge" , "+repage" ,
128+ copr_file
123129 ]
124130
125131 ffmpeg_loudness_args = [
126132 FFMPEG_BIN ,
127133 "-ss" , start_ts , "-to" , end_ts , "-i" , video ,
128- "-filter_complex" , "[0:a]afade=in:d=5 ,afade=out:st={fade_offset3 :.2f}:d=5,adelay=8000:all=1 ,loudnorm=print_format=json" .format (fade_offset3 = fade_offset - 3 ),
134+ "-filter_complex" , "[0:a]afade=in:d={in_:.2f} ,afade=out:st={out_st :.2f}:d={out:.2f} ,loudnorm=print_format=json" .format (in_ = afade_in , out = afade_out , out_st = afade_offset ),
129135 "-f" , "null" , "-"
130136 ]
131137
132138 with open (log_path , "a" ) as error_log :
133139
134140 # Build all the text assets
135- subprocess .run (start_lt_args , stderr = error_log )
136- subprocess .run (start_lt_title_args , stderr = error_log )
137- subprocess .run (start_lt_pres_arg , stderr = error_log )
138- subprocess .run (end_slide_args , stderr = error_log )
139- subprocess .run (end_slide_title_args , stderr = error_log )
140- subprocess .run (end_slide_pres_arg , stderr = error_log )
141+ logger .info ("Building text assets" )
142+ subprocess .run (start_title_args )
143+ subprocess .run (start_pres_arg )
144+ subprocess .run (copyright_args )
141145
142146 # First FFmpeg pass for getting loudness stats
147+ logger .info ("Detecting loudness information" )
143148 logger .debug (ffmpeg_loudness_args )
144149 analysis = subprocess .check_output (ffmpeg_loudness_args , stderr = subprocess .STDOUT ).decode ("utf-8" ).split ("\n " )
145150
@@ -156,42 +161,43 @@ def form_video(video, talk, start_tc, end_tc, framerate = FRAMERATE, out_dir = O
156161 # Run the final build FFmpeg
157162 ffmpeg_args = [
158163 FFMPEG_BIN ,
159- "-ss" , start_ts , "-to" , end_ts , "-i" , video ,
160- "-loop " , "1" , "-framerate " , str (framerate ), "-i" , start_png ,
161- "-stream_loop " , "- 1" , "-r " , str (framerate ), "-i" , "temp/BG_V3.mp4" ,
162- "-loop" , "1" , "-framerate" , str (framerate ), "-i" , "temp/end_pres.png" ,
163- "-loop" , "1" , "-framerate" , str (framerate ), "-i" , "temp/end_title.png" ,
164- "-loop" , "1" , "-framerate" , str (framerate ), "-i" , "temp/sponsor_slide.png" ,
165- "-loop" , "1" , "-framerate" , str (framerate ), "-i" , "temp/start_pres.png" ,
166- "-loop" , "1" , "-framerate" , str (framerate ), "-i" , "temp/start_title.png" ,
167- "-filter_complex" , ("[0:a]afade=in:d=5 ,afade=out:st={fade_offset3 :.2f}:d=5 ,adelay=8000: all=1," .format (fade_offset3 = fade_offset - 3 ) +
168- "loudnorm=I={target:.2f}:TP=-1.5:measured_I={mI}:measured_tp={mTP}:measured_LRA={mLRA}:measured_thresh={mTH}:offset={off}:linear=true:print_format=json[a0 ];" .format (
164+ "-ss" , start_ts , "-to" , end_ts , "-i" , video , #0
165+ "-stream_loop " , "- 1" , "-r " , str (framerate ), "-i" , bkgd_file , #1
166+ "-loop " , "1" , "-framerate " , str (framerate ), "-i" , transp_file , #2
167+ "-loop" , "1" , "-framerate" , str (framerate ), "-i" , spres_file , #3
168+ "-loop" , "1" , "-framerate" , str (framerate ), "-i" , stalk_file , #4
169+ "-loop" , "1" , "-framerate" , str (framerate ), "-i" , logo_file , #5
170+ "-loop" , "1" , "-framerate" , str (framerate ), "-i" , spons_file , #6
171+ "-loop" , "1" , "-framerate" , str (framerate ), "-i" , copr_file , #7
172+ "-filter_complex" , ("[0:a]afade=in:d={in_:.2f} ,afade=out:st={out_st :.2f}:d={out:.2f} ,adelay={title_end:.2f}: all=1," .format (in_ = afade_in , out = afade_out , out_st = afade_offset , spn_dur = spn_dur , title_end = title_end * 1000 ) +
173+ "loudnorm=I={target:.2f}:TP=-1.5:measured_I={mI}:measured_tp={mTP}:measured_LRA={mLRA}:measured_thresh={mTH}:offset={off}:linear=true:print_format=json[a1 ];" .format (
169174 target = LOUD_LEVEL ,
170175 mI = loud_vals ["input_i" ],
171176 mTP = loud_vals ["input_tp" ],
172177 mLRA = loud_vals ["input_lra" ],
173178 mTH = loud_vals ["input_thresh" ],
174179 off = loud_vals ["target_offset" ]) +
175- "[a0]asplit[a1][a2 ];" +
176- "[a2]ebur128=peak=true;" +
177- "[2 :v]crop=w=1920:h=290,fade=in:st=3:d=0.4:alpha=1,fade=out:st=13:d=0.4:alpha=1[ltb ];" +
178- "[0 :v]fade=in:st=0:d =1[v1 ];" +
179- "[6 :v]fade=in:st=3:d=0.4:alpha=1,fade=out:st=13:d=0.4:alpha=1[s2 ];" +
180- "[7:v]fade=in:st=3:d=0.4:alpha=1,fade=out:st=13:d=0.4:alpha=1[s3 ];" +
181- "[v1][ltb]overlay=y=790:x=0:shortest=1[lt1 ];" +
182- "[lt1][s2]overlay=y=970:x=30:shortest=1,settb=1/{framerate :.2f}[v3 ];" .format (framerate = framerate ) +
183- "[v3][s3 ]overlay=y=800:x=30:shortest=1,settb=1/{framerate:.2f}[v4 ];" . format ( framerate = framerate ) +
184- "[2:v]trim=start=0:end={main_end:.2f},settb=1/{framerate:.2f}[ e1];" . format ( framerate = framerate , main_end = end_dur + end_fade ) +
185- "[e1][3 :v]overlay=shortest=1:x=610:y=560 [e2];" +
186- "[e2][4:v]overlay=shortest=1:x=300:y=150[e3 ];" +
187- "[v4][e3 ]xfade=offset={eb_start :.2f}:duration=1,fade=out:st={eb_end :.2f}:d={end_fade :.2f}[m1]; " .format (eb_start = fade_offset , eb_end = fade_offset + end_dur , end_fade = end_fade ) +
188- "[5:v][m1]xfade=offset=8:duration=1,fade=in:d=3[p1]"
180+ "[5:v]split[l1][l2 ];" +
181+ "[1:v]settb=1/{framerate:.2f},split[bg1][bg2];" . format ( framerate = framerate ) +
182+ "[0 :v]settb=1/{framerate:.2f}[m1 ];" . format ( framerate = framerate ) +
183+ "[2 :v][3:v]overlay=x=60:y=640:shortest =1[s2 ];" +
184+ "[s2][4 :v]overlay=x=60:y=320:shortest=1[s3 ];" +
185+ "[s3][l1]overlay=shortest=1[s4 ];" +
186+ "[6:v][s4]xfade=offset={spn_dur:.2f}:duration={spn_fade_out:.2f}[s5 ];" . format ( spn_dur = spn_dur , spn_fade_out = spn_fade_out ) +
187+ "[bg1]trim=start=0:end={title_end :.2f}[bg3 ];" .format (title_end = title_end ) +
188+ "[bg3][s5 ]overlay[s6 ];" +
189+ "[bg2][l2]overlay[ e1];" +
190+ "[e1][7 :v]overlay=x=870:y=70: shortest=1,trim=start=0:end={end_tdur:.2f} [e2];" . format ( end_tdur = end_tdur ) +
191+ "[m1][e2]xfade=offset={eb_start:.2f}:duration=1,fade=out:st={eb_end:.2f}:d={end_fade:.2f}[m2 ];" . format ( eb_start = fade_offset , eb_end = eb_end , end_fade = end_fade ) +
192+ "[s6][m2 ]xfade=offset={title_end :.2f}:duration={title_fade_out :.2f},fade=in :d={spn_fade_in :.2f}[p1] " .format (title_fade_out = title_fade_out , title_end = title_end , spn_fade_in = spn_fade_in )
193+
189194 ),
190195 "-map" , "[p1]" , "-map" , "[a1]" , "-map_metadata" , "-1" ,
191196 "-c:v" , "h264" , "-crf" , "16" , "-g" , str (math .floor (framerate / 2 )), "-flags" , "+cgop" ,
192197 "-c:a" , "aac" , "-ar" , "48000" , "-b:a" , "128k" ,
193198 "-r" , str (framerate ), "-pix_fmt" , "yuv420p" , "-movflags" , "+faststart" , output_path , "-y"
194199 ]
200+ logger .info ("Running main build" )
195201 logger .debug (ffmpeg_args )
196202 subprocess .run (ffmpeg_args , stderr = error_log )
197203
@@ -206,7 +212,7 @@ def ingest_video(input_path, output_dir, framerate = FRAMERATE):
206212 ffmpeg_args = [
207213 FFMPEG_BIN ,
208214 "-i" , input_path ,
209- "-c:v" , "h264" , "-crf" , "12" , "-g" , str (math .floor (framerate / 2 )), "-flags" , "+cgop" ,
215+ "-c:v" , "h264" , "-crf" , "12" , "-g" , str (math .floor (framerate / 2 )), "-flags" , "+cgop" , "-s" , "1920x1080" ,
210216 #"-c:v", "h264_nvenc", "-b:v", "12M",
211217 "-c:a" , "aac" , "-ar" , "48000" , "-b:a" , "128k" ,
212218 "-r" , str (framerate ), "-pix_fmt" , "yuv420p" , "-movflags" , "+faststart" , output_path , "-y"
@@ -240,4 +246,4 @@ def ingest_video(input_path, output_dir, framerate = FRAMERATE):
240246 "presenter" : "Kim M"
241247 }
242248
243- form_video ("static/video/stage_a/bbb_50.mp4" , talk_data , "00:05:00 :00" , "00:06:00 :00" )
249+ form_video ("static/video/stage_a/bbb_50.mp4" , talk_data , "00:05:05 :00" , "00:05:20 :00" )
0 commit comments