diff --git a/requirements.txt b/requirements.txt index 2a4b755..bb086c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ google_auth_oauthlib==1.2.0 Google_Images_Search==1.4.6 gradio==4.19.0 httplib2==0.22.0 -moviepy==1.0.3 +git+https://github.com/OsaAjani/moviepy.git openai==1.13.3 orjson==3.9.15 protobuf==4.25.3 @@ -14,8 +14,7 @@ PyYAML==6.0.1 Requests==2.31.0 SQLAlchemy==2.0.27 git+https://github.com/Paillat-dev/tiktok-uploader.git +torch==2.2.0 TTS==0.22.0 TTS==0.22.0 whisper_timestamped==1.14.4 -Pillow==9.5.0 - diff --git a/src/chore/GenerationContext.py b/src/chore/GenerationContext.py index dbee3f8..58acbbc 100644 --- a/src/chore/GenerationContext.py +++ b/src/chore/GenerationContext.py @@ -3,7 +3,7 @@ import time from datetime import datetime import gradio -import moviepy.editor as mp +import moviepy as mp from .. import engines from ..models import Video, SessionLocal @@ -171,10 +171,10 @@ class GenerationContext: *self.index_8, *self.index_9, ] - clip = mp.CompositeVideoClip(clips, size=(self.width, self.height)) audio = mp.CompositeAudioClip(self.audio) - clip.set_duration(self.duration) - clip: mp.CompositeVideoClip = clip.set_audio(audio) + clip = mp.CompositeVideoClip(clips, size=(self.width, self.height)).with_duration(self.duration).with_audio( + audio + ) clip.write_videofile(self.get_file_path("final.mp4"), fps=60, threads=4, codec="h264_nvenc") self.progress(0.8, "Generating metadata...") @@ -192,7 +192,5 @@ class GenerationContext: self.store_in_db() self.progress(1, "Done!") - if os.name == 'nt': - os.system(f"start {os.path.abspath(self.dir)}") - else: - os.system(f"open {self.dir}") + command = "start" if os.name == 'nt' else "open" + os.system(f"{command} {os.path.abspath(self.dir)}") diff --git a/src/engines/AssetsEngine/DallEAssetsEngine.py b/src/engines/AssetsEngine/DallEAssetsEngine.py index 8be64fb..e726e08 100644 --- a/src/engines/AssetsEngine/DallEAssetsEngine.py +++ b/src/engines/AssetsEngine/DallEAssetsEngine.py @@ -2,11 +2,11 @@ import os from typing import Literal, TypedDict, List import gradio as gr -import moviepy.editor as mp +import moviepy as mp +import moviepy.video.fx as vfx import openai from openai import OpenAI import requests -from moviepy.video.fx.resize import resize from . import BaseAssetsEngine @@ -79,15 +79,11 @@ class DallEAssetsEngine(BaseAssetsEngine): img = mp.ImageClip("temp.png") os.remove("temp.png") - img: mp.ImageClip = img.set_duration(end - start) - img: mp.ImageClip = img.set_start(start) - img: mp.ImageClip = resize(img, width=max_width) - if self.aspect_ratio == "portrait": - img: mp.ImageClip = img.set_position(("center", "top")) - elif self.aspect_ratio == "landscape": - img: mp.ImageClip = img.set_position(("center", "top")) - elif self.aspect_ratio == "square": - img: mp.ImageClip = img.set_position(("center", "top")) + img: mp.ImageClip = img.with_duration(end - start) + img: mp.ImageClip = img.with_start(start) + img: mp.ImageClip = img.with_effects([vfx.Resize(width=max_width)]) + position = img.with_position(("center", "top")) + img: mp.ImageClip = img.with_position(position) clips.append(img) return clips diff --git a/src/engines/AssetsEngine/GoogleAssetsEngine.py b/src/engines/AssetsEngine/GoogleAssetsEngine.py index 61eba8d..9353183 100644 --- a/src/engines/AssetsEngine/GoogleAssetsEngine.py +++ b/src/engines/AssetsEngine/GoogleAssetsEngine.py @@ -3,10 +3,10 @@ import shutil from typing import TypedDict import gradio as gr -import moviepy.editor as mp -from google_images_search import GoogleImagesSearch -from moviepy.video.fx.resize import resize +import moviepy as mp +import moviepy.video.fx as vfx +from google_images_search import GoogleImagesSearch from . import BaseAssetsEngine @@ -38,7 +38,7 @@ class GoogleAssetsEngine(BaseAssetsEngine): super().__init__() def generate(self, options: list[Spec]) -> list[mp.ImageClip]: - max_width = self.ctx.width / 3 * 2 + max_width = int(self.ctx.width / 3 * 2) clips = [] for option in options: query = option["query"] @@ -61,13 +61,11 @@ class GoogleAssetsEngine(BaseAssetsEngine): # delete the temp folder except Exception as e: print(e) + continue finally: shutil.rmtree("temp") - img: mp.ImageClip = img.set_duration(end - start) - img: mp.ImageClip = img.set_start(start) - img: mp.ImageClip = resize(img, width=max_width) - img: mp.ImageClip = img.set_position(("center", "top")) + img = img.with_duration(end - start).with_start(start).with_effects([vfx.Resize(width=max_width)]).with_position(("center", "top")) clips.append(img) return clips diff --git a/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py b/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py index d3b305c..0066ddc 100644 --- a/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py +++ b/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py @@ -4,9 +4,8 @@ import shutil import time import gradio as gr -import moviepy.editor as mp -from moviepy.audio.fx.audio_fadein import audio_fadein -from moviepy.audio.fx.audio_fadeout import audio_fadeout +import moviepy as mp +import moviepy.audio.fx as afx from . import BaseAudioBackgroundEngine @@ -43,18 +42,18 @@ class MusicAudioBackgroundEngine(BaseAudioBackgroundEngine): background = mp.AudioFileClip(f"{self.background_audio.path}") self.ctx.credits += f"\n{self.background_audio.data['credits']}" # we add fade in and fade out to the audio - background: mp.AudioFileClip = background.fx(audio_fadein, 1).fx(audio_fadeout, 1) + background = background.with_effects([afx.AudioFadeIn(1), afx.AudioFadeOut(1)]) # loop the audio to match the duration of the video audio_clips = [] while sum([clip.duration for clip in audio_clips]) < self.ctx.duration: audio_clips.append(background) # now we cut the audio to match the duration of the video exactly - audio: mp.AudioFileClip = mp.concatenate_audioclips(audio_clips) - audio: mp.AudioFileClip = audio.subclip(0, self.ctx.duration) + audio = mp.concatenate_audioclips(audio_clips) + audio = audio.with_subclip(0, self.ctx.duration) # finally we add a new fade OUT only to the audio - audio: mp.AudioFileClip = audio.fx(audio_fadeout, 1) + audio = audio.with_effects([afx.AudioFadeOut(1)]) # change volume to 0.5 - audio: mp.AudioFileClip = audio.volumex(0.5) + audio: mp.AudioFileClip = audio.with_multiply_volume(0.5) self.ctx.audio.append(audio) @classmethod diff --git a/src/engines/BackgroundEngine/VideoBackgroundEngine.py b/src/engines/BackgroundEngine/VideoBackgroundEngine.py index 4531343..eb151d0 100644 --- a/src/engines/BackgroundEngine/VideoBackgroundEngine.py +++ b/src/engines/BackgroundEngine/VideoBackgroundEngine.py @@ -4,9 +4,8 @@ import shutil import time import gradio as gr -import moviepy.editor as mp -from moviepy.video.fx.crop import crop -from moviepy.video.fx.resize import resize +import moviepy as mp +import moviepy.video.fx as vfx from . import BaseBackgroundEngine @@ -47,14 +46,16 @@ class VideoBackgroundEngine(BaseBackgroundEngine): "The background video is shorter than the video to be generated." ) start = random.uniform(0, background_max_start) - clip = background.subclip(start, start + self.ctx.duration) + clip = background.with_subclip(start, start + self.ctx.duration) self.ctx.credits += f"\n{self.background_video.data['credits']}" - clip = resize(clip, width=self.ctx.width, height=self.ctx.height) w, h = clip.size - if w > h: - clip = crop(clip, width=self.ctx.width, height=self.ctx.height, x_center=w / 2, y_center=h / 2) + resolution: float = w / h + canvas_resolution: float = self.ctx.width / self.ctx.height + if resolution > canvas_resolution: + clip = clip.with_effects([vfx.Resize(height=self.ctx.height)]) else: - clip = crop(clip, width=self.ctx.width, height=self.ctx.height, x_center=w / 2, y_center=h / 2) + clip = clip.with_effects([vfx.Resize(width=self.ctx.width)]) + clip = clip.with_position(("center", "center")) self.ctx.index_0.append(clip) @classmethod diff --git a/src/engines/BaseEngine.py b/src/engines/BaseEngine.py index 05fecbf..08ba531 100644 --- a/src/engines/BaseEngine.py +++ b/src/engines/BaseEngine.py @@ -1,6 +1,6 @@ from abc import ABC, abstractmethod -import moviepy.editor as mp +import moviepy as mp from sqlalchemy.future import select from ..chore import GenerationContext diff --git a/src/engines/CaptioningEngine/SimpleCaptioningEngine.py b/src/engines/CaptioningEngine/SimpleCaptioningEngine.py index 104df2e..d0cd304 100644 --- a/src/engines/CaptioningEngine/SimpleCaptioningEngine.py +++ b/src/engines/CaptioningEngine/SimpleCaptioningEngine.py @@ -1,10 +1,37 @@ import gradio as gr import os -import shutil -from moviepy.editor import TextClip +import platform +import moviepy as mp from . import BaseCaptioningEngine +def get_available_fonts(): + #on windows, the fonts are in the C:\Windows\Fonts and C:\Users\Username\AppData\Local\Microsoft\Windows\Fonts + #on linux, the fonts are in the /usr/share/fonts directory + #on mac, the fonts are in the /Library/Fonts, /System/Library/Fonts, and ~/Library/Fonts directories + if platform.system() == "Windows": + font_dirs = [ + "C:\\Windows\\Fonts", + "C:\\Users\\{}\\AppData\\Local\\Microsoft\\Windows\\Fonts".format(os.getlogin()), + ] + elif platform.system() == "Linux": + font_dirs = ["/usr/share/fonts"] + elif platform.system() == "Darwin": + font_dirs = [ + "/Library/Fonts", + "/System/Library/Fonts", + "{}/Library/Fonts".format(os.path.expanduser("~")), + ] + else: + font_dirs = [] + fonts = [] + for font_dir in font_dirs: + for root, dirs, files in os.walk(font_dir): + for file in files: + if file.endswith(".ttf") or file.endswith(".otf"): + fonts.append(file) + return list(set(fonts)) + class SimpleCaptioningEngine(BaseCaptioningEngine): name = "SimpleCaptioningEngine" @@ -20,21 +47,22 @@ class SimpleCaptioningEngine(BaseCaptioningEngine): super().__init__() - def build_caption_object(self, text: str, start: float, end: float) -> TextClip: + def build_caption_object(self, text: str, start: float, end: float) -> mp.TextClip: return ( - TextClip( - text, - fontsize=self.font_size, + mp.TextClip( + text=text, + font_size=self.font_size, color=self.font_color, font=self.font, stroke_color=self.stroke_color, stroke_width=self.stroke_width, method="caption", - size=(self.ctx.width / 3 * 2, None), + size=(int(self.ctx.width / 3 * 2), None), + text_align="center", ) - .set_position(("center", 0.65), relative=True) - .set_start(start) - .set_duration(end - start) + .with_position(("center", 0.65), relative=True) + .with_start(start) + .with_duration(end - start) ) def ends_with_punctuation(self, text: str) -> bool: @@ -95,7 +123,7 @@ class SimpleCaptioningEngine(BaseCaptioningEngine): with gr.Group(): font = gr.Dropdown( label="Font", - choices=TextClip.list("font"), + choices=get_available_fonts(), value="Comic-Sans-MS-Bold", allow_custom_value=True, # Allow custom font ) diff --git a/src/engines/ScriptEngine/CustomScriptEngine.py b/src/engines/ScriptEngine/CustomScriptEngine.py index a485f8b..df32f5c 100644 --- a/src/engines/ScriptEngine/CustomScriptEngine.py +++ b/src/engines/ScriptEngine/CustomScriptEngine.py @@ -19,8 +19,8 @@ class CustomScriptEngine(BaseScriptEngine): def get_options(cls) -> list: return [ gr.Textbox( - label="Prompt", - placeholder="Enter your prompt here", + label="Script", + placeholder="Enter your script here", value="", ) ] diff --git a/src/engines/ScriptEngine/ScientificFactsScriptEngine.py b/src/engines/ScriptEngine/ScientificFactsScriptEngine.py index 0446258..ed3c93d 100644 --- a/src/engines/ScriptEngine/ScientificFactsScriptEngine.py +++ b/src/engines/ScriptEngine/ScientificFactsScriptEngine.py @@ -7,7 +7,7 @@ from ...utils.prompting import get_prompt class ScientificFactsScriptEngine(BaseScriptEngine): - name = "Scientific facts Thoughts" + name = "Scientific facts" description = "Generate a scientific facts script." num_options = 1 diff --git a/src/engines/TTSEngine/BaseTTSEngine.py b/src/engines/TTSEngine/BaseTTSEngine.py index e3955c4..ddea648 100644 --- a/src/engines/TTSEngine/BaseTTSEngine.py +++ b/src/engines/TTSEngine/BaseTTSEngine.py @@ -1,7 +1,7 @@ from abc import abstractmethod from typing import TypedDict -import moviepy.editor as mp +import moviepy as mp import whisper_timestamped as wt from torch.cuda import is_available diff --git a/src/engines/TTSEngine/CoquiTTSEngine.py b/src/engines/TTSEngine/CoquiTTSEngine.py index df64ded..32e3b5d 100644 --- a/src/engines/TTSEngine/CoquiTTSEngine.py +++ b/src/engines/TTSEngine/CoquiTTSEngine.py @@ -89,7 +89,7 @@ class CoquiTTSEngine(BaseTTSEngine): "ko", # Korean "hi", # Hindi ] - num_options = 4 + num_options = 5 def __init__(self, options: list): super().__init__() @@ -99,7 +99,7 @@ class CoquiTTSEngine(BaseTTSEngine): self.to_force_duration = options[2] self.duration = options[3] - os.environ["COQUI_TOS_AGREED"] = options[4] + os.environ["COQUI_TOS_AGREED"] = str(options[4]) try: self.tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2") except: diff --git a/src/engines/UploadEngine/TikTokUploadEngine.py b/src/engines/UploadEngine/TikTokUploadEngine.py index 6c72a16..6345b72 100644 --- a/src/engines/UploadEngine/TikTokUploadEngine.py +++ b/src/engines/UploadEngine/TikTokUploadEngine.py @@ -25,7 +25,9 @@ class TikTokUploadEngine(BaseUploadEngine): description: str = self.ctx.description hashtags = self.hashtags.strip().split(" ") - # extract any hashtags from the description / title and remove them from the description + title = title.strip() + description = description.strip() + #we get all the hastags from title and description and add them to the list of hashtags, while removing them from the title and description for word in title.split(): if word.startswith("#"): hashtags.append(word) @@ -35,12 +37,10 @@ class TikTokUploadEngine(BaseUploadEngine): hashtags.append(word) description = description.replace(word, "") - title = title.strip() - description = description.strip() hashtags_str = " ".join(hashtags) + " " if hashtags else "" failed = upload_video( filename=self.ctx.get_file_path("final.mp4"), - description=f"{title}\n{description} {hashtags_str}", + description=f"{title} {hashtags_str}\n{description}", cookies_str=cookies, browser="chrome", comment=True, stitch=False, duet=False