@ -1,33 +1,66 @@
import multiprocessing
import os
import os
import re
import re
import multiprocessing
from os . path import exists
from typing import Tuple , Any , Final
import translators as ts
import shutil
import shutil
from os . path import exists
from typing import Final
from typing import Tuple , Any
from typing import Tuple , Any
from PIL import Image
from moviepy . audio . AudioClip import concatenate_audioclips , CompositeAudioClip
import ffmpeg
from moviepy . audio . io . AudioFileClip import AudioFileClip
import translators as ts
from moviepy . video . VideoClip import ImageClip
from PIL import Image
from moviepy . video . compositing . CompositeVideoClip import CompositeVideoClip
from moviepy . video . compositing . concatenate import concatenate_videoclips
from moviepy . video . io . VideoFileClip import VideoFileClip
from moviepy . video . io . ffmpeg_tools import ffmpeg_extract_subclip
from rich . console import Console
from rich . console import Console
from rich . progress import track
from rich . progress import track
from utils import settings
from utils . cleanup import cleanup
from utils . cleanup import cleanup
from utils . console import print_step , print_substep
from utils . console import print_step , print_substep
from utils . video import Video
from utils . videos import save_data
from utils . thumbnail import create_thumbnail
from utils import settings
from utils . thumbnail import create_thumbnail
from utils . thumbnail import create_thumbnail
from utils . videos import save_data
console = Console ( )
console = Console ( )
import tempfile
import threading
import time
class ProgressFfmpeg ( threading . Thread ) :
def __init__ ( self , vid_duration_seconds , progress_update_callback ) :
threading . Thread . __init__ ( self , name = " ProgressFfmpeg " )
self . stop_event = threading . Event ( )
self . output_file = tempfile . NamedTemporaryFile ( mode = " w+ " , delete = False )
self . vid_duration_seconds = vid_duration_seconds
self . progress_update_callback = progress_update_callback
def run ( self ) :
while not self . stop_event . is_set ( ) :
latest_progress = self . get_latest_ms_progress ( )
if latest_progress is not None :
completed_percent = latest_progress / self . vid_duration_seconds
self . progress_update_callback ( completed_percent )
time . sleep ( 1 )
def get_latest_ms_progress ( self ) :
lines = self . output_file . readlines ( )
if lines :
for line in lines :
if " out_time_ms " in line :
out_time_ms = line . split ( " = " ) [ 1 ]
return int ( out_time_ms ) / 1000000.0
return None
def stop ( self ) :
self . stop_event . set ( )
def __enter__ ( self ) :
self . start ( )
return self
def __exit__ ( self , * args , * * kwargs ) :
self . stop ( )
def name_normalize ( name : str ) - > str :
def name_normalize ( name : str ) - > str :
name = re . sub ( r ' [? \\ " % *:|<>] ' , " " , name )
name = re . sub ( r ' [? \\ " % *:|<>] ' , " " , name )
@ -46,22 +79,25 @@ def name_normalize(name: str) -> str:
return name
return name
def prepare_background ( reddit_id : str , W : int , H : int ) - > VideoFileClip :
def prepare_background ( reddit_id : str , W : int , H : int ) - > str :
clip = (
output_path = f " assets/temp/ { reddit_id } /background_noaudio.mp4 "
VideoFileClip ( f " assets/temp/ { reddit_id } /background.mp4 " )
output = (
. without_audio ( )
ffmpeg . input ( f " assets/temp/ { reddit_id } /background.mp4 " )
. resize ( height = H )
. filter ( " crop " , f " ih*( { W } / { H } ) " , " ih " )
. output (
output_path ,
an = None ,
* * {
" c:v " : " h264 " ,
" b:v " : " 20M " ,
" b:a " : " 192k " ,
" threads " : multiprocessing . cpu_count ( ) ,
} ,
)
. overwrite_output ( )
)
)
output . run ( quiet = True )
# calculate the center of the background clip
return output_path
c = clip . w / / 2
# calculate the coordinates where to crop
half_w = W / / 2
x1 = c - half_w
x2 = c + half_w
return clip . crop ( x1 = x1 , y1 = 0 , x2 = x2 , y2 = H )
def make_final_video (
def make_final_video (
@ -81,103 +117,136 @@ def make_final_video(
W : Final [ int ] = int ( settings . config [ " settings " ] [ " resolution_w " ] )
W : Final [ int ] = int ( settings . config [ " settings " ] [ " resolution_w " ] )
H : Final [ int ] = int ( settings . config [ " settings " ] [ " resolution_h " ] )
H : Final [ int ] = int ( settings . config [ " settings " ] [ " resolution_h " ] )
# try: # if it isn't found (i.e you just updated and copied over config.toml) it will throw an error
# VOLUME_MULTIPLIER = settings.config["settings"]['background']["background_audio_volume"]
# except (TypeError, KeyError):
# print('No background audio volume found in config.toml. Using default value of 1.')
# VOLUME_MULTIPLIER = 1
reddit_id = re . sub ( r " [^ \ w \ s-] " , " " , reddit_obj [ " thread_id " ] )
reddit_id = re . sub ( r " [^ \ w \ s-] " , " " , reddit_obj [ " thread_id " ] )
print_step ( " Creating the final video 🎥 " )
print_step ( " Creating the final video 🎥 " )
VideoFileClip . reW = lambda clip : clip . resize ( width = W )
background_clip = ffmpeg . input ( prepare_background ( reddit_id , W = W , H = H ) )
VideoFileClip . reH = lambda clip : clip . resize ( width = H )
opacity = settings . config [ " settings " ] [ " opacity " ]
transition = settings . config [ " settings " ] [ " transition " ]
background_clip = prepare_background ( reddit_id , W = W , H = H )
# Gather all audio clips
# Gather all audio clips
audio_clips = list ( )
if settings . config [ " settings " ] [ " storymode " ] :
if settings . config [ " settings " ] [ " storymode " ] :
if settings . config [ " settings " ] [ " storymodemethod " ] == 0 :
if settings . config [ " settings " ] [ " storymodemethod " ] == 0 :
audio_clips = [ AudioFileClip ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) ]
audio_clips = [ ffmpeg . input ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) ]
audio_clips . insert ( 1 , AudioFileClip ( f " assets/temp/ { reddit_id } /mp3/postaudio.mp3 " ) )
audio_clips . insert (
1 , ffmpeg . input ( f " assets/temp/ { reddit_id } /mp3/postaudio.mp3 " )
)
elif settings . config [ " settings " ] [ " storymodemethod " ] == 1 :
elif settings . config [ " settings " ] [ " storymodemethod " ] == 1 :
audio_clips = [
audio_clips = [
AudioFileClip ( f " assets/temp/ { reddit_id } /mp3/postaudio- { i } .mp3 " )
ffmpeg. input ( f " assets/temp/ { reddit_id } /mp3/postaudio- { i } .mp3 " )
for i in track (
for i in track (
range ( number_of_clips + 1 ) , " Collecting the audio files... "
range ( number_of_clips + 1 ) , " Collecting the audio files... "
)
)
]
]
audio_clips . insert ( 0 , AudioFileClip ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) )
audio_clips . insert (
0 , ffmpeg . input ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " )
)
else :
else :
audio_clips = [
audio_clips = [
AudioFileClip ( f " assets/temp/ { reddit_id } /mp3/ { i } .mp3 " )
ffmpeg. input ( f " assets/temp/ { reddit_id } /mp3/ { i } .mp3 " )
for i in range ( number_of_clips )
for i in range ( number_of_clips )
]
]
audio_clips . insert ( 0 , AudioFileClip ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) )
audio_clips . insert ( 0 , ffmpeg . input ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) )
audio_concat = concatenate_audioclips ( audio_clips )
audio_composite = CompositeAudioClip ( [ audio_concat ] )
audio_clips_durations = [
float (
ffmpeg . probe ( f " assets/temp/ { reddit_id } /mp3/ { i } .mp3 " ) [ " format " ] [
" duration "
]
)
for i in range ( number_of_clips )
]
audio_clips_durations . insert (
0 ,
float (
ffmpeg . probe ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) [ " format " ] [
" duration "
]
) ,
)
audio_concat = ffmpeg . concat ( * audio_clips , a = 1 , v = 0 )
ffmpeg . output (
audio_concat , f " assets/temp/ { reddit_id } /audio.mp3 " , * * { " b:a " : " 192k " }
) . overwrite_output ( ) . run ( quiet = True )
console . log ( f " [bold green] Video Will Be: { length } Seconds Long " )
console . log ( f " [bold green] Video Will Be: { length } Seconds Long " )
# add title to video
# Create a screenshot_width variable to scale the screenshots to the correct size, the calculation is int((W * 90) // 100)
image_clips = [ ]
# Convert it to a ffmpeg one with iw-
# Gather all images
screenshot_width = int ( ( W * 45 ) / / 100 )
new_opacity = 1 if opacity is None or float ( opacity ) > = 1 else float ( opacity )
audio = ffmpeg . input ( f " assets/temp/ { reddit_id } /audio.mp3 " )
new_transition = (
0 if transition is None or float ( transition ) > 2 else float ( transition )
image_clips = list ( )
)
screenshot_width = int ( ( W * 90 ) / / 100 )
image_clips . insert (
image_clips . insert (
0 ,
0 ,
ImageClip ( f " assets/temp/ { reddit_id } /png/title.png " )
ffmpeg . input ( f " assets/temp/ { reddit_id } /png/title.png " ) [ " v " ] . filter (
. set_duration ( audio_clips [ 0 ] . duration )
" scale " , screenshot_width , - 1
. resize ( width = screenshot_width )
) ,
. set_opacity ( new_opacity )
. crossfadein ( new_transition )
. crossfadeout ( new_transition ) ,
)
)
current_time = 0
if settings . config [ " settings " ] [ " storymode " ] :
if settings . config [ " settings " ] [ " storymode " ] :
audio_clips_durations = [
float (
ffmpeg . probe ( f " assets/temp/ { reddit_id } /mp3/postaudio- { i } .mp3 " ) [
" format "
] [ " duration " ]
)
for i in range ( number_of_clips )
]
audio_clips_durations . insert (
0 ,
float (
ffmpeg . probe ( f " assets/temp/ { reddit_id } /mp3/title.mp3 " ) [ " format " ] [
" duration "
]
) ,
)
if settings . config [ " settings " ] [ " storymodemethod " ] == 0 :
if settings . config [ " settings " ] [ " storymodemethod " ] == 0 :
image_clips . insert (
image_clips . insert (
1 ,
1 ,
ImageClip ( f " assets/temp/ { reddit_id } /png/story_content.png " )
ffmpeg . input ( f " assets/temp/ { reddit_id } /png/story_content.png " ) . filter (
. set_duration ( audio_clips [ 1 ] . duration )
" scale " , screenshot_width , - 1
. set_position ( " center " )
) ,
. resize ( width = screenshot_width )
. set_opacity ( float ( opacity ) ) ,
)
)
background_clip = background_clip . overlay (
image_clips [ 1 ] ,
enable = f " between(t, { current_time } , { current_time + audio_clips_durations [ 1 ] } ) " ,
x = " (main_w-overlay_w)/2 " ,
y = " (main_h-overlay_h)/2 " ,
)
current_time + = audio_clips_durations [ 1 ]
elif settings . config [ " settings " ] [ " storymodemethod " ] == 1 :
elif settings . config [ " settings " ] [ " storymodemethod " ] == 1 :
for i in track (
for i in track (
range ( 0 , number_of_clips + 1 ) , " Collecting the image files... "
range ( 0 , number_of_clips + 1 ) , " Collecting the image files... "
) :
) :
image_clips . append (
image_clips . append (
ImageClip ( f " assets/temp/ { reddit_id } /png/img { i } .png " )
ffmpeg . input ( f " assets/temp/ { reddit_id } /png/img { i } .png " ) [ " v " ] . filter (
. set_duration ( audio_clips [ i + 1 ] . duration )
" scale " , 1080 , - 1
. resize ( width = screenshot_width )
)
. set_opacity ( new_opacity )
)
# .crossfadein(new_transition)
background_clip = background_clip . overlay (
# .crossfadeout(new_transition)
image_clips [ i ] ,
enable = f " between(t, { current_time } , { current_time + audio_clips_durations [ i ] } ) " ,
x = " (main_w-overlay_w)/2 " ,
y = " (main_h-overlay_h)/2 " ,
)
)
current_time + = audio_clips_durations [ i ]
else :
else :
for i in range ( 0 , number_of_clips ) :
for i in range ( 0 , number_of_clips + 1 ) :
image_clips . append (
image_clips . append (
ImageClip ( f " assets/temp/ { reddit_id } /png/comment_ { i } .png " )
ffmpeg . input ( f " assets/temp/ { reddit_id } /png/comment_ { i } .png " ) [
. set_duration ( audio_clips [ i + 1 ] . duration )
" v "
. resize ( width = screenshot_width )
] . filter ( " scale " , screenshot_width , - 1 )
. set_opacity ( new_opacity )
)
. crossfadein ( new_transition )
background_clip = background_clip . overlay (
. crossfadeout ( new_transition )
image_clips [ i ] ,
enable = f " between(t, { current_time } , { current_time + audio_clips_durations [ i ] } ) " ,
x = " (main_w-overlay_w)/2 " ,
y = " (main_h-overlay_h)/2 " ,
)
)
current_time + = audio_clips_durations [ i ]
img_clip_pos = background_config [ 3 ]
image_concat = concatenate_videoclips ( image_clips ) . set_position (
img_clip_pos
) # note transition kwarg for delay in imgs
image_concat . audio = audio_composite
final = CompositeVideoClip ( [ background_clip , image_concat ] )
title = re . sub ( r " [^ \ w \ s-] " , " " , reddit_obj [ " thread_title " ] )
title = re . sub ( r " [^ \ w \ s-] " , " " , reddit_obj [ " thread_title " ] )
idx = re . sub ( r " [^ \ w \ s-] " , " " , reddit_obj [ " thread_id " ] )
idx = re . sub ( r " [^ \ w \ s-] " , " " , reddit_obj [ " thread_id " ] )
title_thumb = reddit_obj [ " thread_title " ]
title_thumb = reddit_obj [ " thread_title " ]
@ -189,13 +258,12 @@ def make_final_video(
print_substep ( " The results folder didn ' t exist so I made it " )
print_substep ( " The results folder didn ' t exist so I made it " )
os . makedirs ( f " ./results/ { subreddit } " )
os . makedirs ( f " ./results/ { subreddit } " )
# create a t umbnail for the video
# create a t h umbnail for the video
settingsbackground = settings . config [ " settings " ] [ " background " ]
settingsbackground = settings . config [ " settings " ] [ " background " ]
if settingsbackground [ " background_thumbnail " ] :
if settingsbackground [ " background_thumbnail " ] :
if not exists ( f " ./results/ { subreddit } /thumbnails " ) :
if not exists ( f " ./results/ { subreddit } /thumbnails " ) :
print_substep (
print_substep ( " The results/thumbnails folder didn ' t exist so I made it " )
" The results/thumbnails folder didn ' t exist so I made it " )
os . makedirs ( f " ./results/ { subreddit } /thumbnails " )
os . makedirs ( f " ./results/ { subreddit } /thumbnails " )
# get the first file with the .png extension from assets/backgrounds and use it as a background for the thumbnail
# get the first file with the .png extension from assets/backgrounds and use it as a background for the thumbnail
first_image = next (
first_image = next (
@ -209,23 +277,72 @@ def make_final_video(
if first_image is None :
if first_image is None :
print_substep ( " No png files found in assets/backgrounds " , " red " )
print_substep ( " No png files found in assets/backgrounds " , " red " )
if settingsbackground [ " background_thumbnail " ] and first_image :
else :
font_family = settingsbackground [ " background_thumbnail_font_family " ]
font_family = settingsbackground [ " background_thumbnail_font_family " ]
font_size = settingsbackground [ " background_thumbnail_font_size " ]
font_size = settingsbackground [ " background_thumbnail_font_size " ]
font_color = settingsbackground [ " background_thumbnail_font_color " ]
font_color = settingsbackground [ " background_thumbnail_font_color " ]
thumbnail = Image . open ( f " assets/backgrounds/ { first_image } " )
thumbnail = Image . open ( f " assets/backgrounds/ { first_image } " )
width , height = thumbnail . size
width , height = thumbnail . size
thumbnailSave = create_thumbnail ( thumbnail , font_family , font_size , font_color , width , height , title_thumb )
thumbnailSave = create_thumbnail (
thumbnailSave . save ( f " ./assets/temp/ { reddit_id } /thumbnail.png " )
thumbnail ,
print_substep ( f " Thumbnail - Building Thumbnail in assets/temp/ { reddit_id } /thumbnail.png " )
font_family ,
font_size ,
font_color ,
width ,
height ,
title_thumb ,
)
thumbnailSave . save ( f " ./assets/temp/ { reddit_id } /thumbnail.png " )
print_substep (
f " Thumbnail - Building Thumbnail in assets/temp/ { reddit_id } /thumbnail.png "
)
# create a tumbnail for the video
text = f " Background by { background_config [ 2 ] } "
settingsbackground = settings . config [ " settings " ] [ " background " ]
background_clip = ffmpeg . drawtext (
background_clip ,
text = text ,
x = f " (w-text_w) " ,
y = f " (h-text_h) " ,
fontsize = 12 ,
fontcolor = " White " ,
fontfile = os . path . join ( " fonts " , " Roboto-Regular.ttf " ) ,
)
print_step ( " Rendering the video 🎥 " )
from tqdm import tqdm
pbar = tqdm ( total = 100 , desc = " Progress: " , bar_format = " {l_bar} {bar} " , unit = " % " )
def on_update_example ( progress ) :
status = round ( progress * 100 , 2 )
old_percentage = pbar . n
pbar . update ( status - old_percentage )
with ProgressFfmpeg ( length , on_update_example ) as progress :
ffmpeg . output (
background_clip ,
audio ,
f " results/ { subreddit } / { filename } .mp4 " ,
f = " mp4 " ,
* * {
" c:v " : " h264 " ,
" b:v " : " 20M " ,
" b:a " : " 192k " ,
" threads " : multiprocessing . cpu_count ( ) ,
} ,
) . overwrite_output ( ) . global_args ( " -progress " , progress . output_file . name ) . run (
quiet = True ,
overwrite_output = True ,
capture_stdout = False ,
capture_stderr = False ,
)
old_percentage = pbar . n
pbar . update ( 100 - old_percentage )
pbar . close ( )
if settingsbackground [ " background_thumbnail " ] :
if settingsbackground [ " background_thumbnail " ] :
if not exists ( f " ./results/ { subreddit } /thumbnails " ) :
if not exists ( f " ./results/ { subreddit } /thumbnails " ) :
print_substep (
print_substep ( " The results/thumbnails folder didn ' t exist so I made it " )
" The results/thumbnails folder didn ' t exist so I made it " )
os . makedirs ( f " ./results/ { subreddit } /thumbnails " )
os . makedirs ( f " ./results/ { subreddit } /thumbnails " )
# get the first file with the .png extension from assets/backgrounds and use it as a background for the thumbnail
# get the first file with the .png extension from assets/backgrounds and use it as a background for the thumbnail
first_image = next (
first_image = next (
@ -245,43 +362,24 @@ def make_final_video(
font_color = settingsbackground [ " background_thumbnail_font_color " ]
font_color = settingsbackground [ " background_thumbnail_font_color " ]
thumbnail = Image . open ( f " assets/backgrounds/ { first_image } " )
thumbnail = Image . open ( f " assets/backgrounds/ { first_image } " )
width , height = thumbnail . size
width , height = thumbnail . size
thumbnailSave = create_thumbnail ( thumbnail , font_family , font_size , font_color , width , height , title_thumb )
thumbnailSave = create_thumbnail (
thumbnail , font_family , font_size , font_color , width , height , title_thumb
)
thumbnailSave . save ( f " ./assets/temp/ { reddit_id } /thumbnail.png " )
thumbnailSave . save ( f " ./assets/temp/ { reddit_id } /thumbnail.png " )
print_substep ( f " Thumbnail - Building Thumbnail in assets/temp/ { reddit_id } /thumbnail.png " )
print_substep (
f " Thumbnail - Building Thumbnail in assets/temp/ { reddit_id } /thumbnail.png "
# if settings.config["settings"]['background']["background_audio"] and exists(f"assets/backgrounds/background.mp3"):
)
# audioclip = mpe.AudioFileClip(f"assets/backgrounds/background.mp3").set_duration(final.duration)
# audioclip = audioclip.fx( volumex, 0.2)
# get the thumbnail image from assets/temp/id/thumbnail.png and save it in results/subreddit/thumbnails
# final_audio = mpe.CompositeAudioClip([final.audio, audioclip])
if settingsbackground [ " background_thumbnail " ] and exists (
# # lowered_audio = audio_background.multiply_volume( # todo get this to work
f " assets/temp/ { reddit_id } /thumbnail.png "
# # VOLUME_MULTIPLIER) # lower volume by background_audio_volume, use with fx
) :
# final.set_audio(final_audio)
shutil . move (
f " assets/temp/ { reddit_id } /thumbnail.png " ,
final = Video ( final ) . add_watermark (
f " ./results/ { subreddit } /thumbnails/ { filename } .png " ,
text = f " Background credit: { background_config [ 2 ] } " ,
)
opacity = 0.4 ,
redditid = reddit_obj ,
)
final . write_videofile (
f " assets/temp/ { reddit_id } /temp.mp4 " ,
fps = int ( settings . config [ " settings " ] [ " fps " ] ) ,
audio_codec = " aac " ,
audio_bitrate = " 192k " ,
verbose = False ,
threads = multiprocessing . cpu_count ( ) ,
#preset="ultrafast", # for testing purposes
)
ffmpeg_extract_subclip (
f " assets/temp/ { reddit_id } /temp.mp4 " ,
0 ,
length ,
targetname = f " results/ { subreddit } / { filename } .mp4 " ,
)
#get the thumbnail image from assets/temp/id/thumbnail.png and save it in results/subreddit/thumbnails
if settingsbackground [ " background_thumbnail " ] and exists ( f " assets/temp/ { id } /thumbnail.png " ) :
shutil . move ( f " assets/temp/ { id } /thumbnail.png " , f " ./results/ { subreddit } /thumbnails/ { filename } .png " )
save_data ( subreddit , filename + " .mp4 " , title , idx , background_config [ 2 ] )
save_data ( subreddit , filename + " .mp4 " , title , idx , background_config [ 2 ] )
print_step ( " Removing temporary files 🗑 " )
print_step ( " Removing temporary files 🗑 " )
cleanups = cleanup ( reddit_id )
cleanups = cleanup ( reddit_id )
print_substep ( f " Removed { cleanups } temporary files 🗑 " )
print_substep ( f " Removed { cleanups } temporary files 🗑 " )