Merge pull request #1409 from Xpl0itU/develop
final_video.py: ffmpeg-python port
This commit is contained in:
8 files changed
+222
-275
No files matched your search
Binary file not shown.
+1
-1
@@ -124,7 +124,7 @@ def get_subreddit_threads(POST_ID: str):
|
||||
threadurl = f"https://reddit.com{submission.permalink}"
|
||||
|
||||
print_substep(f"Video will be: {submission.title} :thumbsup:", style="bold green")
|
||||
print_substep(f"Thread url is : {threadurl } :thumbsup:", style="bold green")
|
||||
print_substep(f"Thread url is: {threadurl} :thumbsup:", style="bold green")
|
||||
print_substep(f"Thread has {upvotes} upvotes", style="bold blue")
|
||||
print_substep(f"Thread has a upvote ratio of {ratio}%", style="bold blue")
|
||||
print_substep(f"Thread has {num_comments} comments", style="bold blue")
|
||||
|
||||
+2
-1
@@ -18,4 +18,5 @@ clean-text==0.6.0
|
||||
unidecode==1.3.2
|
||||
spacy==3.4.1
|
||||
torch==1.12.1
|
||||
transformers==4.25.1
|
||||
transformers==4.25.1
|
||||
ffmpeg-python==0.2.0
|
||||
@@ -22,14 +22,13 @@ ai_similarity_keywords = {optional = true, type="str", example= 'Elon Musk, Twit
|
||||
|
||||
[settings]
|
||||
allow_nsfw = { optional = false, type = "bool", default = false, example = false, options = [true, false, ], explanation = "Whether to allow NSFW content, True or False" }
|
||||
theme = { optional = false, default = "dark", example = "light", options = ["dark", "light", ], explanation = "Sets the Reddit theme, either LIGHT or DARK" }
|
||||
theme = { optional = false, default = "dark", example = "light", options = ["dark", "light", "transparent", ], explanation = "Sets the Reddit theme, either LIGHT or DARK. For story mode you can also use a transparent background." }
|
||||
times_to_run = { optional = false, default = 1, example = 2, explanation = "Used if you want to run multiple times. Set to an int e.g. 4 or 29 or 1", type = "int", nmin = 1, oob_error = "It's very hard to run something less than once." }
|
||||
opacity = { optional = false, default = 0.9, example = 0.8, explanation = "Sets the opacity of the comments when overlayed over the background", type = "float", nmin = 0, nmax = 1, oob_error = "The opacity HAS to be between 0 and 1", input_error = "The opacity HAS to be a decimal number between 0 and 1" }
|
||||
transition = { optional = true, default = 0.2, example = 0.2, explanation = "Sets the transition time (in seconds) between the comments. Set to 0 if you want to disable it.", type = "float", nmin = 0, nmax = 2, oob_error = "The transition HAS to be between 0 and 2", input_error = "The opacity HAS to be a decimal number between 0 and 2" }
|
||||
storymode = { optional = true, type = "bool", default = false, example = false, options = [true, false,], explanation = "Only read out title and post content, great for subreddits with stories" }
|
||||
storymodemethod= { optional = true, default = 1, example = 1, explanation = "Style that's used for the storymode. Set to 0 for single picture display in whole video, set to 1 for fancy looking video ", type = "int", nmin = 0, oob_error = "It's very hard to run something less than once.", options = [0, 1] }
|
||||
storymode_max_length = { optional = true, default = 1000, example = 1000, explanation = "Max length of the storymode video in characters. 200 characters are approximately 50 seconds.", type = "int", nmin = 1, oob_error = "It's very hard to make a video under a second." }
|
||||
fps = { optional = false, default = 30, example = 30, explanation = "Sets the FPS of the video, 30 is default for best performance. 60 FPS is smoother.", type = "int", nmin = 1, nmax = 60, oob_error = "The FPS HAS to be between 1 and 60" }
|
||||
resolution_w = { optional = false, default = 1080, example = 1440, explantation = "Sets the width in pixels of the final video" }
|
||||
resolution_h = { optional = false, default = 1920, example = 2560, explantation = "Sets the height in pixels of the final video" }
|
||||
|
||||
|
||||
+23
-40
@@ -6,7 +6,8 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
from rich.progress import track
|
||||
from TTS.engine_wrapper import process_text
|
||||
|
||||
def draw_multiple_line_text(image, text, font, text_color, padding, wrap=50) -> None:
|
||||
|
||||
def draw_multiple_line_text(image, text, font, text_color, padding, wrap=50, transparent=False) -> None:
|
||||
"""
|
||||
Draw multiline text over given image
|
||||
"""
|
||||
@@ -14,63 +15,45 @@ def draw_multiple_line_text(image, text, font, text_color, padding, wrap=50) ->
|
||||
Fontperm = font.getsize(text)
|
||||
image_width, image_height = image.size
|
||||
lines = textwrap.wrap(text, width=wrap)
|
||||
y = (image_height / 2) - (
|
||||
((Fontperm[1] + (len(lines) * padding) / len(lines)) * len(lines)) / 2
|
||||
)
|
||||
y = (image_height / 2) - (((Fontperm[1] + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
|
||||
for line in lines:
|
||||
line_width, line_height = font.getsize(line)
|
||||
if transparent:
|
||||
shadowcolor = "black"
|
||||
for i in range(1, 5):
|
||||
draw.text(((image_width - line_width) / 2 - i, y - i), line, font=font, fill=shadowcolor)
|
||||
draw.text(((image_width - line_width) / 2 + i, y - i), line, font=font, fill=shadowcolor)
|
||||
draw.text(((image_width - line_width) / 2 - i, y + i), line, font=font, fill=shadowcolor)
|
||||
draw.text(((image_width - line_width) / 2 + i, y + i), line, font=font, fill=shadowcolor)
|
||||
draw.text(((image_width - line_width) / 2, y), line, font=font, fill=text_color)
|
||||
y += line_height + padding
|
||||
|
||||
|
||||
# theme=bgcolor,reddit_obj=reddit_object,txtclr=txtcolor
|
||||
def imagemaker(theme, reddit_obj: dict, txtclr, padding=5) -> None:
|
||||
def imagemaker(theme, reddit_obj: dict, txtclr, padding=5, transparent=False) -> None:
|
||||
"""
|
||||
Render Images for video
|
||||
"""
|
||||
title = process_text(reddit_obj["thread_title"], False) #TODO if second argument cause any error
|
||||
title = process_text(reddit_obj["thread_title"], False) # TODO if second argument cause any error
|
||||
texts = reddit_obj["thread_post"]
|
||||
id = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
|
||||
|
||||
tfont = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), 27) # for title
|
||||
font = ImageFont.truetype(
|
||||
os.path.join("fonts", "Roboto-Regular.ttf"), 20
|
||||
) # for despcription|comments
|
||||
size = (500, 176)
|
||||
if transparent:
|
||||
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), 50)
|
||||
tfont = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), 50)
|
||||
else:
|
||||
tfont = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), 35) # for title
|
||||
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Regular.ttf"), 30)
|
||||
size = (1920, 1080)
|
||||
|
||||
image = Image.new("RGBA", size, theme)
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# for title
|
||||
if len(title) > 40:
|
||||
draw_multiple_line_text(image, title, tfont, txtclr, padding, wrap=30)
|
||||
else:
|
||||
|
||||
Fontperm = tfont.getsize(title)
|
||||
draw.text(
|
||||
((image.size[0] - Fontperm[0]) / 2, (image.size[1] - Fontperm[1]) / 2),
|
||||
font=tfont,
|
||||
text=title,
|
||||
) # (image.size[1]/2)-(Fontperm[1]/2)
|
||||
draw_multiple_line_text(image, title, tfont, txtclr, padding, wrap=30, transparent=transparent)
|
||||
|
||||
image.save(f"assets/temp/{id}/png/title.png")
|
||||
|
||||
# for comment|description
|
||||
|
||||
for idx, text in track(enumerate(texts), "Rendering Image"):#, total=len(texts)):
|
||||
|
||||
for idx, text in track(enumerate(texts), "Rendering Image"):
|
||||
image = Image.new("RGBA", size, theme)
|
||||
draw = ImageDraw.Draw(image)
|
||||
text = process_text(text,False)
|
||||
if len(text) > 50:
|
||||
draw_multiple_line_text(image, text, font, txtclr, padding)
|
||||
|
||||
else:
|
||||
|
||||
Fontperm = font.getsize(text)
|
||||
draw.text(
|
||||
((image.size[0] - Fontperm[0]) / 2, (image.size[1] - Fontperm[1]) / 2),
|
||||
font=font,
|
||||
text=text,
|
||||
) # (image.size[1]/2)-(Fontperm[1]/2)
|
||||
text = process_text(text, False)
|
||||
draw_multiple_line_text(image, text, font, txtclr, padding, wrap=30, transparent=transparent)
|
||||
image.save(f"assets/temp/{id}/png/img{idx}.png")
|
||||
@@ -1,68 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Tuple
|
||||
|
||||
from PIL import ImageFont, Image, ImageDraw, ImageEnhance
|
||||
from moviepy.video.VideoClip import VideoClip, ImageClip
|
||||
from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
|
||||
|
||||
|
||||
class Video:
|
||||
def __init__(self, video: VideoClip, *args, **kwargs):
|
||||
self.video: VideoClip = video
|
||||
self.fps = self.video.fps
|
||||
self.duration = self.video.duration
|
||||
|
||||
@staticmethod
|
||||
def _create_watermark(text, redditid, fontsize, opacity=0.5):
|
||||
id = re.sub(r"[^\w\s-]", "", redditid["thread_id"])
|
||||
path = f"./assets/temp/{id}/png/watermark.png"
|
||||
width = int(fontsize * len(text))
|
||||
height = int(fontsize * len(text) / 2)
|
||||
white = (255, 255, 255)
|
||||
transparent = (0, 0, 0, 0)
|
||||
|
||||
font = ImageFont.load_default()
|
||||
wm = Image.new("RGBA", (width, height), transparent)
|
||||
im = Image.new("RGBA", (width, height), transparent) # Change this line too.
|
||||
|
||||
draw = ImageDraw.Draw(wm)
|
||||
w, h = draw.textsize(text, font)
|
||||
draw.text(((width - w) / 2, (height - h) / 2), text, white, font)
|
||||
en = ImageEnhance.Brightness(wm) # todo allow it to use the fontsize
|
||||
mask = en.enhance(1 - opacity)
|
||||
im.paste(wm, (25, 25), mask)
|
||||
im.save(path)
|
||||
return ImageClip(path)
|
||||
|
||||
def add_watermark(
|
||||
self,
|
||||
text,
|
||||
redditid,
|
||||
opacity=0.5,
|
||||
duration: int | float = 5,
|
||||
position: Tuple = (0.7, 0.9),
|
||||
fontsize=15,
|
||||
):
|
||||
compensation = round(
|
||||
(
|
||||
position[0]
|
||||
/ ((len(text) * (fontsize / 5) / 1.5) / 100 + position[0] * position[0])
|
||||
),
|
||||
ndigits=2,
|
||||
)
|
||||
position = (compensation, position[1])
|
||||
# print(f'{compensation=}')
|
||||
# print(f'{position=}')
|
||||
img_clip = self._create_watermark(
|
||||
text, redditid, fontsize=fontsize, opacity=opacity
|
||||
)
|
||||
img_clip = img_clip.set_opacity(opacity).set_duration(duration)
|
||||
img_clip = img_clip.set_position(
|
||||
position, relative=True
|
||||
) # todo get dara from utils/CONSTANTS.py and adapt position accordingly
|
||||
|
||||
# Overlay the img clip on the first video clip
|
||||
self.video = CompositeVideoClip([self.video, img_clip])
|
||||
return self.video
|
||||
Executable → Regular
+178
-162
@@ -1,33 +1,67 @@
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
import multiprocessing
|
||||
from os.path import exists
|
||||
from typing import Tuple, Any, Final
|
||||
import translators as ts
|
||||
import shutil
|
||||
from os.path import exists
|
||||
from typing import Final
|
||||
from typing import Tuple, Any
|
||||
from PIL import Image
|
||||
|
||||
from moviepy.audio.AudioClip import concatenate_audioclips, CompositeAudioClip
|
||||
from moviepy.audio.io.AudioFileClip import AudioFileClip
|
||||
from moviepy.video.VideoClip import ImageClip
|
||||
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
|
||||
import ffmpeg
|
||||
import translators as ts
|
||||
from PIL import Image
|
||||
from rich.console import Console
|
||||
from rich.progress import track
|
||||
|
||||
from utils import settings
|
||||
from utils.cleanup import cleanup
|
||||
from utils.console import print_step, print_substep
|
||||
from utils.video import Video
|
||||
from utils.thumbnail import create_thumbnail
|
||||
from utils.videos import save_data
|
||||
from utils.thumbnail import create_thumbnail
|
||||
from utils import settings
|
||||
from utils.thumbnail import create_thumbnail
|
||||
|
||||
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:
|
||||
name = re.sub(r'[?\\"%*:|<>]', "", name)
|
||||
@@ -46,29 +80,20 @@ def name_normalize(name: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def prepare_background(reddit_id: str, W: int, H: int) -> VideoFileClip:
|
||||
clip = (
|
||||
VideoFileClip(f"assets/temp/{reddit_id}/background.mp4")
|
||||
.without_audio()
|
||||
.resize(height=H)
|
||||
)
|
||||
|
||||
# calculate the center of the background clip
|
||||
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 prepare_background(reddit_id: str, W: int, H: int) -> str:
|
||||
output_path = f"assets/temp/{reddit_id}/background_noaudio.mp4"
|
||||
output = ffmpeg.input(f"assets/temp/{reddit_id}/background.mp4").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)
|
||||
return output_path
|
||||
|
||||
|
||||
def make_final_video(
|
||||
number_of_clips: int,
|
||||
length: int,
|
||||
reddit_obj: dict,
|
||||
background_config: Tuple[str, str, str, Any],
|
||||
number_of_clips: int,
|
||||
length: int,
|
||||
reddit_obj: dict,
|
||||
background_config: Tuple[str, str, str, Any],
|
||||
):
|
||||
"""Gathers audio clips, gathers all screenshots, stitches them together and saves the final video to assets/temp
|
||||
Args:
|
||||
@@ -81,103 +106,93 @@ def make_final_video(
|
||||
W: Final[int] = int(settings.config["settings"]["resolution_w"])
|
||||
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"])
|
||||
print_step("Creating the final video 🎥")
|
||||
|
||||
VideoFileClip.reW = lambda clip: clip.resize(width=W)
|
||||
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)
|
||||
background_clip = ffmpeg.input(prepare_background(reddit_id, W=W, H=H))
|
||||
|
||||
# Gather all audio clips
|
||||
audio_clips = list()
|
||||
if settings.config["settings"]["storymode"]:
|
||||
if settings.config["settings"]["storymodemethod"] == 0:
|
||||
audio_clips = [AudioFileClip(f"assets/temp/{reddit_id}/mp3/title.mp3")]
|
||||
audio_clips.insert(1, AudioFileClip(f"assets/temp/{reddit_id}/mp3/postaudio.mp3"))
|
||||
audio_clips = [ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3")]
|
||||
audio_clips.insert(1, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/postaudio.mp3"))
|
||||
elif settings.config["settings"]["storymodemethod"] == 1:
|
||||
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(
|
||||
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:
|
||||
audio_clips = [
|
||||
AudioFileClip(f"assets/temp/{reddit_id}/mp3/{i}.mp3")
|
||||
for i in range(number_of_clips)
|
||||
]
|
||||
audio_clips.insert(0, AudioFileClip(f"assets/temp/{reddit_id}/mp3/title.mp3"))
|
||||
audio_concat = concatenate_audioclips(audio_clips)
|
||||
audio_composite = CompositeAudioClip([audio_concat])
|
||||
audio_clips = [ffmpeg.input(f"assets/temp/{reddit_id}/mp3/{i}.mp3") for i in range(number_of_clips)]
|
||||
audio_clips.insert(0, ffmpeg.input(f"assets/temp/{reddit_id}/mp3/title.mp3"))
|
||||
|
||||
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")
|
||||
# add title to video
|
||||
image_clips = []
|
||||
# Gather all images
|
||||
new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity)
|
||||
new_transition = (
|
||||
0 if transition is None or float(transition) > 2 else float(transition)
|
||||
)
|
||||
screenshot_width = int((W * 90) // 100)
|
||||
# Create a screenshot_width variable to scale the screenshots to the correct size, the calculation is int((W * 90) // 100)
|
||||
# Convert it to a ffmpeg one with iw-
|
||||
screenshot_width = int((W * 45) // 100)
|
||||
audio = ffmpeg.input(f"assets/temp/{reddit_id}/audio.mp3")
|
||||
|
||||
image_clips = list()
|
||||
|
||||
image_clips.insert(
|
||||
0,
|
||||
ImageClip(f"assets/temp/{reddit_id}/png/title.png")
|
||||
.set_duration(audio_clips[0].duration)
|
||||
.resize(width=screenshot_width)
|
||||
.set_opacity(new_opacity)
|
||||
.crossfadein(new_transition)
|
||||
.crossfadeout(new_transition),
|
||||
ffmpeg.input(f"assets/temp/{reddit_id}/png/title.png")['v']
|
||||
.filter('scale', screenshot_width, -1)
|
||||
)
|
||||
|
||||
current_time = 0
|
||||
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:
|
||||
image_clips.insert(
|
||||
1,
|
||||
ImageClip(f"assets/temp/{reddit_id}/png/story_content.png")
|
||||
.set_duration(audio_clips[1].duration)
|
||||
.set_position("center")
|
||||
.resize(width=screenshot_width)
|
||||
.set_opacity(float(opacity)),
|
||||
ffmpeg.input(f"assets/temp/{reddit_id}/png/story_content.png")
|
||||
.filter('scale', screenshot_width, -1)
|
||||
)
|
||||
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:
|
||||
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(
|
||||
ImageClip(f"assets/temp/{reddit_id}/png/img{i}.png")
|
||||
.set_duration(audio_clips[i + 1].duration)
|
||||
.resize(width=screenshot_width)
|
||||
.set_opacity(new_opacity)
|
||||
# .crossfadein(new_transition)
|
||||
# .crossfadeout(new_transition)
|
||||
ffmpeg.input(f"assets/temp/{reddit_id}/png/img{i}.png")['v']
|
||||
.filter('scale', 1080, -1)
|
||||
)
|
||||
background_clip = background_clip.overlay(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:
|
||||
for i in range(0, number_of_clips):
|
||||
for i in range(0, number_of_clips + 1):
|
||||
image_clips.append(
|
||||
ImageClip(f"assets/temp/{reddit_id}/png/comment_{i}.png")
|
||||
.set_duration(audio_clips[i + 1].duration)
|
||||
.resize(width=screenshot_width)
|
||||
.set_opacity(new_opacity)
|
||||
.crossfadein(new_transition)
|
||||
.crossfadeout(new_transition)
|
||||
ffmpeg.input(f"assets/temp/{reddit_id}/png/comment_{i}.png")['v']
|
||||
.filter('scale', screenshot_width, -1)
|
||||
)
|
||||
background_clip = background_clip.overlay(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"])
|
||||
idx = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
|
||||
title_thumb = reddit_obj["thread_title"]
|
||||
@@ -189,9 +204,68 @@ def make_final_video(
|
||||
print_substep("The results folder didn't exist so I made it")
|
||||
os.makedirs(f"./results/{subreddit}")
|
||||
|
||||
# create a tumbnail for the video
|
||||
# create a thumbnail for the video
|
||||
settingsbackground = settings.config["settings"]["background"]
|
||||
|
||||
if settingsbackground["background_thumbnail"]:
|
||||
if not exists(f"./results/{subreddit}/thumbnails"):
|
||||
print_substep(
|
||||
"The results/thumbnails folder didn't exist so I made it")
|
||||
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
|
||||
first_image = next(
|
||||
(
|
||||
file
|
||||
for file in os.listdir("assets/backgrounds")
|
||||
if file.endswith(".png")
|
||||
),
|
||||
None,
|
||||
)
|
||||
if first_image is None:
|
||||
print_substep("No png files found in assets/backgrounds", "red")
|
||||
|
||||
else:
|
||||
font_family = settingsbackground["background_thumbnail_font_family"]
|
||||
font_size = settingsbackground["background_thumbnail_font_size"]
|
||||
font_color = settingsbackground["background_thumbnail_font_color"]
|
||||
thumbnail = Image.open(f"assets/backgrounds/{first_image}")
|
||||
width, height = thumbnail.size
|
||||
thumbnailSave = create_thumbnail(thumbnail, 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")
|
||||
|
||||
text = f"Background by {background_config[2]}"
|
||||
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)
|
||||
|
||||
position = W // 2, H - 20
|
||||
ffmpeg.filter(filter_name='drawtext', stream_spec=background_clip, text=text, fontfile='fonts/Roboto-Regular.ttf', fontsize=12,
|
||||
fontcolor='white', x=position[0], y=position[1], box=1)
|
||||
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 not exists(f"./results/{subreddit}/thumbnails"):
|
||||
print_substep(
|
||||
@@ -219,69 +293,11 @@ def make_final_video(
|
||||
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
|
||||
settingsbackground = settings.config["settings"]["background"]
|
||||
# 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/{reddit_id}/thumbnail.png"):
|
||||
shutil.move(f"assets/temp/{reddit_id}/thumbnail.png", f"./results/{subreddit}/thumbnails/{filename}.png")
|
||||
|
||||
if settingsbackground["background_thumbnail"]:
|
||||
if not exists(f"./results/{subreddit}/thumbnails"):
|
||||
print_substep(
|
||||
"The results/thumbnails folder didn't exist so I made it")
|
||||
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
|
||||
first_image = next(
|
||||
(
|
||||
file
|
||||
for file in os.listdir("assets/backgrounds")
|
||||
if file.endswith(".png")
|
||||
),
|
||||
None,
|
||||
)
|
||||
if first_image is None:
|
||||
print_substep("No png files found in assets/backgrounds", "red")
|
||||
|
||||
if settingsbackground["background_thumbnail"] and first_image:
|
||||
font_family = settingsbackground["background_thumbnail_font_family"]
|
||||
font_size = settingsbackground["background_thumbnail_font_size"]
|
||||
font_color = settingsbackground["background_thumbnail_font_color"]
|
||||
thumbnail = Image.open(f"assets/backgrounds/{first_image}")
|
||||
width, height = thumbnail.size
|
||||
thumbnailSave = create_thumbnail(thumbnail, 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")
|
||||
|
||||
# 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)
|
||||
# final_audio = mpe.CompositeAudioClip([final.audio, audioclip])
|
||||
# # lowered_audio = audio_background.multiply_volume( # todo get this to work
|
||||
# # VOLUME_MULTIPLIER) # lower volume by background_audio_volume, use with fx
|
||||
# final.set_audio(final_audio)
|
||||
|
||||
final = Video(final).add_watermark(
|
||||
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 🗑")
|
||||
cleanups = cleanup(reddit_id)
|
||||
print_substep(f"Removed {cleanups} temporary files 🗑")
|
||||
|
||||
@@ -59,15 +59,31 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
|
||||
)
|
||||
bgcolor = (33, 33, 36, 255)
|
||||
txtcolor = (240, 240, 240)
|
||||
transparent = False
|
||||
elif settings.config["settings"]["theme"] == "transparent":
|
||||
if storymode:
|
||||
# Transparent theme
|
||||
bgcolor = (0, 0, 0, 0)
|
||||
txtcolor = (255, 255, 255)
|
||||
transparent = True
|
||||
else:
|
||||
# Switch to dark theme
|
||||
cookie_file = open(
|
||||
"./video_creation/data/cookie-dark-mode.json", encoding="utf-8"
|
||||
)
|
||||
bgcolor = (33, 33, 36, 255)
|
||||
txtcolor = (240, 240, 240)
|
||||
transparent = False
|
||||
else:
|
||||
cookie_file = open(
|
||||
"./video_creation/data/cookie-light-mode.json", encoding="utf-8"
|
||||
)
|
||||
bgcolor = (255, 255, 255, 255)
|
||||
txtcolor = (0, 0, 0)
|
||||
transparent = False
|
||||
if storymode and settings.config["settings"]["storymodemethod"] == 1:
|
||||
# for idx,item in enumerate(reddit_object["thread_post"]):
|
||||
return imagemaker(theme=bgcolor, reddit_obj=reddit_object, txtclr=txtcolor)
|
||||
return imagemaker(theme=bgcolor, reddit_obj=reddit_object, txtclr=txtcolor, transparent=transparent)
|
||||
cookies = json.load(cookie_file)
|
||||
cookie_file.close()
|
||||
|
||||
|
||||
Reference in new issue
Block a user