chore: release 3.4.0 (#2426)
Co-authored-by: Jason Cameron <git@jasoncameron.dev> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: RequieMa <57754488+RequieMa@users.noreply.github.com> Co-authored-by: prafful <praffulsharma1230@gmail.com> Co-authored-by: M. Sanaullah <thebolly@gmail.com> Co-authored-by: embee <emilien.bev.com@gmail.com> Co-authored-by: Rodrigo <55567123+rodrigodasilv@users.noreply.github.com> Co-authored-by: Emilien Bevierre <44171454+emilienbev@users.noreply.github.com> Co-authored-by: tkhmielnitzky <tkhmielnitzky@gmail.com> Co-authored-by: bnfone <89687390+bnfone@users.noreply.github.com> Co-authored-by: Cyteon <129582290+Cyteon@users.noreply.github.com>
This commit is contained in:
21 files changed
+320
-165
No files matched your search
@@ -6,7 +6,7 @@ from random import randrange
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import yt_dlp
|
||||
from moviepy.editor import AudioFileClip, VideoFileClip
|
||||
from moviepy import AudioFileClip, VideoFileClip
|
||||
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip
|
||||
|
||||
from utils import settings
|
||||
@@ -14,26 +14,26 @@ from utils.console import print_step, print_substep
|
||||
|
||||
|
||||
def load_background_options():
|
||||
background_options = {}
|
||||
_background_options = {}
|
||||
# Load background videos
|
||||
with open("./utils/background_videos.json") as json_file:
|
||||
background_options["video"] = json.load(json_file)
|
||||
_background_options["video"] = json.load(json_file)
|
||||
|
||||
# Load background audios
|
||||
with open("./utils/background_audios.json") as json_file:
|
||||
background_options["audio"] = json.load(json_file)
|
||||
_background_options["audio"] = json.load(json_file)
|
||||
|
||||
# Remove "__comment" from backgrounds
|
||||
del background_options["video"]["__comment"]
|
||||
del background_options["audio"]["__comment"]
|
||||
del _background_options["video"]["__comment"]
|
||||
del _background_options["audio"]["__comment"]
|
||||
|
||||
for name in list(background_options["video"].keys()):
|
||||
pos = background_options["video"][name][3]
|
||||
for name in list(_background_options["video"].keys()):
|
||||
pos = _background_options["video"][name][3]
|
||||
|
||||
if pos != "center":
|
||||
background_options["video"][name][3] = lambda t: ("center", pos + t)
|
||||
_background_options["video"][name][3] = lambda t: ("center", pos + t)
|
||||
|
||||
return background_options
|
||||
return _background_options
|
||||
|
||||
|
||||
def get_start_and_end_times(video_length: int, length_of_clip: int) -> Tuple[int, int]:
|
||||
@@ -124,10 +124,11 @@ def chop_background(background_config: Dict[str, Tuple], video_length: int, redd
|
||||
"""Generates the background audio and footage to be used in the video and writes it to assets/temp/background.mp3 and assets/temp/background.mp4
|
||||
|
||||
Args:
|
||||
reddit_object (Dict[str,str]) : Reddit object
|
||||
background_config (Dict[str,Tuple]]) : Current background configuration
|
||||
video_length (int): Length of the clip where the background footage is to be taken out of
|
||||
"""
|
||||
id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"])
|
||||
thread_id = re.sub(r"[^\w\s-]", "", reddit_object["thread_id"])
|
||||
|
||||
if settings.config["settings"]["background"][f"background_audio_volume"] == 0:
|
||||
print_step("Volume was set to 0. Skipping background audio creation . . .")
|
||||
@@ -138,8 +139,8 @@ def chop_background(background_config: Dict[str, Tuple], video_length: int, redd
|
||||
start_time_audio, end_time_audio = get_start_and_end_times(
|
||||
video_length, background_audio.duration
|
||||
)
|
||||
background_audio = background_audio.subclip(start_time_audio, end_time_audio)
|
||||
background_audio.write_audiofile(f"assets/temp/{id}/background.mp3")
|
||||
background_audio = background_audio.subclipped(start_time_audio, end_time_audio)
|
||||
background_audio.write_audiofile(f"assets/temp/{thread_id}/background.mp3")
|
||||
|
||||
print_step("Finding a spot in the backgrounds video to chop...✂️")
|
||||
video_choice = f"{background_config['video'][2]}-{background_config['video'][1]}"
|
||||
@@ -149,17 +150,18 @@ def chop_background(background_config: Dict[str, Tuple], video_length: int, redd
|
||||
)
|
||||
# Extract video subclip
|
||||
try:
|
||||
with VideoFileClip(f"assets/backgrounds/video/{video_choice}") as video:
|
||||
new = video.subclipped(start_time_video, end_time_video)
|
||||
new.write_videofile(f"assets/temp/{thread_id}/background.mp4")
|
||||
|
||||
except (OSError, IOError): # ffmpeg issue see #348
|
||||
print_substep("FFMPEG issue. Trying again...")
|
||||
ffmpeg_extract_subclip(
|
||||
f"assets/backgrounds/video/{video_choice}",
|
||||
start_time_video,
|
||||
end_time_video,
|
||||
targetname=f"assets/temp/{id}/background.mp4",
|
||||
outputfile=f"assets/temp/{thread_id}/background.mp4",
|
||||
)
|
||||
except (OSError, IOError): # ffmpeg issue see #348
|
||||
print_substep("FFMPEG issue. Trying again...")
|
||||
with VideoFileClip(f"assets/backgrounds/video/{video_choice}") as video:
|
||||
new = video.subclip(start_time_video, end_time_video)
|
||||
new.write_videofile(f"assets/temp/{id}/background.mp4")
|
||||
print_substep("Background video chopped successfully!", style="bold green")
|
||||
return background_config["video"][2]
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from utils import settings
|
||||
from utils.cleanup import cleanup
|
||||
from utils.console import print_step, print_substep
|
||||
from utils.fonts import getheight
|
||||
from utils.id import extract_id
|
||||
from utils.thumbnail import create_thumbnail
|
||||
from utils.videos import save_data
|
||||
|
||||
@@ -92,7 +93,7 @@ def prepare_background(reddit_id: str, W: int, H: int) -> str:
|
||||
output_path,
|
||||
an=None,
|
||||
**{
|
||||
"c:v": "h264",
|
||||
"c:v": "h264_nvenc",
|
||||
"b:v": "20M",
|
||||
"b:a": "192k",
|
||||
"threads": multiprocessing.cpu_count(),
|
||||
@@ -108,19 +109,61 @@ def prepare_background(reddit_id: str, W: int, H: int) -> str:
|
||||
return output_path
|
||||
|
||||
|
||||
def get_text_height(draw, text, font, max_width):
|
||||
lines = textwrap.wrap(text, width=max_width)
|
||||
total_height = 0
|
||||
for line in lines:
|
||||
_, _, _, height = draw.textbbox((0, 0), line, font=font)
|
||||
total_height += height
|
||||
return total_height
|
||||
|
||||
|
||||
def create_fancy_thumbnail(image, text, text_color, padding, wrap=35):
|
||||
"""
|
||||
It will take the 1px from the middle of the template and will be resized (stretched) vertically to accommodate the extra height needed for the title.
|
||||
"""
|
||||
print_step(f"Creating fancy thumbnail for: {text}")
|
||||
font_title_size = 47
|
||||
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
|
||||
image_width, image_height = image.size
|
||||
lines = textwrap.wrap(text, width=wrap)
|
||||
y = (
|
||||
(image_height / 2)
|
||||
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
|
||||
+ 30
|
||||
)
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# Calculate text height to determine new image height
|
||||
draw = ImageDraw.Draw(image)
|
||||
text_height = get_text_height(draw, text, font, wrap)
|
||||
lines = textwrap.wrap(text, width=wrap)
|
||||
# This is -50 to reduce the empty space at the bottom of the image,
|
||||
# change it as per your requirement if needed otherwise leave it.
|
||||
new_image_height = image_height + text_height + padding * (len(lines) - 1) - 50
|
||||
|
||||
# Separate the image into top, middle (1px), and bottom parts
|
||||
top_part_height = image_height // 2
|
||||
middle_part_height = 1 # 1px height middle section
|
||||
bottom_part_height = image_height - top_part_height - middle_part_height
|
||||
|
||||
top_part = image.crop((0, 0, image_width, top_part_height))
|
||||
middle_part = image.crop((0, top_part_height, image_width, top_part_height + middle_part_height))
|
||||
bottom_part = image.crop((0, top_part_height + middle_part_height, image_width, image_height))
|
||||
|
||||
# Stretch the middle part
|
||||
new_middle_height = new_image_height - top_part_height - bottom_part_height
|
||||
middle_part = middle_part.resize((image_width, new_middle_height))
|
||||
|
||||
# Create new image with the calculated height
|
||||
new_image = Image.new("RGBA", (image_width, new_image_height))
|
||||
|
||||
# Paste the top, stretched middle, and bottom parts into the new image
|
||||
new_image.paste(top_part, (0, 0))
|
||||
new_image.paste(middle_part, (0, top_part_height))
|
||||
new_image.paste(bottom_part, (0, top_part_height + new_middle_height))
|
||||
|
||||
# Draw the title text on the new image
|
||||
draw = ImageDraw.Draw(new_image)
|
||||
y = top_part_height + padding
|
||||
for line in lines:
|
||||
draw.text((120, y), line, font=font, fill=text_color, align="left")
|
||||
y += get_text_height(draw, line, font, wrap) + padding
|
||||
|
||||
# Draw the username "PlotPulse" at the specific position
|
||||
username_font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), 30)
|
||||
draw.text(
|
||||
(205, 825),
|
||||
@@ -130,39 +173,7 @@ def create_fancy_thumbnail(image, text, text_color, padding, wrap=35):
|
||||
align="left",
|
||||
)
|
||||
|
||||
if len(lines) == 3:
|
||||
lines = textwrap.wrap(text, width=wrap + 10)
|
||||
font_title_size = 40
|
||||
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
|
||||
y = (
|
||||
(image_height / 2)
|
||||
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
|
||||
+ 35
|
||||
)
|
||||
elif len(lines) == 4:
|
||||
lines = textwrap.wrap(text, width=wrap + 10)
|
||||
font_title_size = 35
|
||||
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
|
||||
y = (
|
||||
(image_height / 2)
|
||||
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
|
||||
+ 40
|
||||
)
|
||||
elif len(lines) > 4:
|
||||
lines = textwrap.wrap(text, width=wrap + 10)
|
||||
font_title_size = 30
|
||||
font = ImageFont.truetype(os.path.join("fonts", "Roboto-Bold.ttf"), font_title_size)
|
||||
y = (
|
||||
(image_height / 2)
|
||||
- (((getheight(font, text) + (len(lines) * padding) / len(lines)) * len(lines)) / 2)
|
||||
+ 30
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
draw.text((120, y), line, font=font, fill=text_color, align="left")
|
||||
y += getheight(font, line) + padding
|
||||
|
||||
return image
|
||||
return new_image
|
||||
|
||||
|
||||
def merge_background_audio(audio: ffmpeg, reddit_id: str):
|
||||
@@ -204,7 +215,7 @@ def make_final_video(
|
||||
|
||||
opacity = settings.config["settings"]["opacity"]
|
||||
|
||||
reddit_id = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
|
||||
reddit_id = extract_id(reddit_obj)
|
||||
|
||||
allowOnlyTTSFolder: bool = (
|
||||
settings.config["settings"]["background"]["enable_extra_audio"]
|
||||
@@ -343,8 +354,8 @@ def make_final_video(
|
||||
)
|
||||
current_time += audio_clips_durations[i]
|
||||
|
||||
title = re.sub(r"[^\w\s-]", "", reddit_obj["thread_title"])
|
||||
idx = re.sub(r"[^\w\s-]", "", reddit_obj["thread_id"])
|
||||
title = extract_id(reddit_obj, "thread_title")
|
||||
idx = extract_id(reddit_obj)
|
||||
title_thumb = reddit_obj["thread_title"]
|
||||
|
||||
filename = f"{name_normalize(title)[:251]}"
|
||||
@@ -427,7 +438,7 @@ def make_final_video(
|
||||
path,
|
||||
f="mp4",
|
||||
**{
|
||||
"c:v": "h264",
|
||||
"c:v": "h264_nvenc",
|
||||
"b:v": "20M",
|
||||
"b:a": "192k",
|
||||
"threads": multiprocessing.cpu_count(),
|
||||
@@ -457,7 +468,7 @@ def make_final_video(
|
||||
path,
|
||||
f="mp4",
|
||||
**{
|
||||
"c:v": "h264",
|
||||
"c:v": "h264_nvenc",
|
||||
"b:v": "20M",
|
||||
"b:a": "192k",
|
||||
"threads": multiprocessing.cpu_count(),
|
||||
|
||||
@@ -34,7 +34,7 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
|
||||
# ! Make sure the reddit screenshots folder exists
|
||||
Path(f"assets/temp/{reddit_id}/png").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# set the theme and disable non-essential cookies
|
||||
# set the theme and turn off non-essential cookies
|
||||
if settings.config["settings"]["theme"] == "dark":
|
||||
cookie_file = open("./video_creation/data/cookie-dark-mode.json", encoding="utf-8")
|
||||
bgcolor = (33, 33, 36, 255)
|
||||
@@ -60,7 +60,6 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
|
||||
transparent = False
|
||||
|
||||
if storymode and settings.config["settings"]["storymodemethod"] == 1:
|
||||
# for idx,item in enumerate(reddit_object["thread_post"]):
|
||||
print_substep("Generating images...")
|
||||
return imagemaker(
|
||||
theme=bgcolor,
|
||||
@@ -82,11 +81,15 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
|
||||
dsf = (W // 600) + 1
|
||||
|
||||
context = browser.new_context(
|
||||
locale=lang or "en-us",
|
||||
locale=lang or "en-CA,en;q=0.9",
|
||||
color_scheme="dark",
|
||||
viewport=ViewportSize(width=W, height=H),
|
||||
device_scale_factor=dsf,
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
|
||||
user_agent=f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{browser.version}.0.0.0 Safari/537.36",
|
||||
extra_http_headers={
|
||||
"Dnt": "1",
|
||||
"Sec-Ch-Ua": '"Not A(Brand";v="8", "Chromium";v="132", "Google Chrome";v="132"',
|
||||
},
|
||||
)
|
||||
cookies = json.load(cookie_file)
|
||||
cookie_file.close()
|
||||
@@ -107,17 +110,12 @@ def get_screenshots_of_reddit_posts(reddit_object: dict, screenshot_num: int):
|
||||
|
||||
login_error_div = page.locator(".AnimatedForm__errorMessage").first
|
||||
if login_error_div.is_visible():
|
||||
login_error_message = login_error_div.inner_text()
|
||||
if login_error_message.strip() == "":
|
||||
# The div element is empty, no error
|
||||
pass
|
||||
else:
|
||||
# The div contains an error message
|
||||
print_substep(
|
||||
"Your reddit credentials are incorrect! Please modify them accordingly in the config.toml file.",
|
||||
style="red",
|
||||
)
|
||||
exit()
|
||||
|
||||
print_substep(
|
||||
"Your reddit credentials are incorrect! Please modify them accordingly in the config.toml file.",
|
||||
style="red",
|
||||
)
|
||||
exit()
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ from TTS.aws_polly import AWSPolly
|
||||
from TTS.elevenlabs import elevenlabs
|
||||
from TTS.engine_wrapper import TTSEngine
|
||||
from TTS.GTTS import GTTS
|
||||
from TTS.openai_tts import OpenAITTS
|
||||
from TTS.pyttsx import pyttsx
|
||||
from TTS.streamlabs_polly import StreamlabsPolly
|
||||
from TTS.TikTok import TikTok
|
||||
@@ -21,6 +22,7 @@ TTSProviders = {
|
||||
"TikTok": TikTok,
|
||||
"pyttsx": pyttsx,
|
||||
"ElevenLabs": elevenlabs,
|
||||
"OpenAI": OpenAITTS,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user