diff --git a/.gitignore b/.gitignore
index 65d700d..d4d844f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,11 +145,83 @@ dmypy.json
cython_debug/
# PyCharm
-# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
-# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
-# and can be added to the global gitignore or merged into this file. For a more nuclear
-# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
output/
local/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000..c007ce1
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ sqlite.xerial
+ true
+ org.sqlite.JDBC
+ jdbc:sqlite:C:\Users\jerem\D\09._AI_projects\viralautomator\local\database\db.db
+ $ProjectFileDir$
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..5753022
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..568ed59
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/viralautomator.iml b/.idea/viralautomator.iml
new file mode 100644
index 0000000..1a19fd3
--- /dev/null
+++ b/.idea/viralautomator.iml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/chore/GenerationContext.py b/src/chore/GenerationContext.py
index f8ab19d..6ae208a 100644
--- a/src/chore/GenerationContext.py
+++ b/src/chore/GenerationContext.py
@@ -2,6 +2,7 @@ import os
import time
from datetime import datetime
+import gradio
import moviepy.editor as mp
from .. import engines
@@ -35,8 +36,16 @@ class GenerationContext:
backgroundengine,
metadataengine,
uploadengine,
+ audiobackgroundengine,
progress,
) -> None:
+ self.captions = []
+ self.dir = None
+ self.script = None
+ self.description = None
+ self.title = None
+ self.credits = None
+ self.duration = None
self.progress = progress
self.powerfulllmengine: engines.LLMEngine.BaseLLMEngine = powerfulllmengine[0]
@@ -79,17 +88,13 @@ class GenerationContext:
for eng in self.uploadengine:
eng.ctx = self
- def setup_dir(self):
- self.dir = f"output/{time.time()}"
- os.makedirs(self.dir)
+ self.audiobackgroundengine: engines.AudioBackgroundEngine.BaseAudioBackgroundEngine = (
+ audiobackgroundengine[0]
+ )
+ self.audiobackgroundengine.ctx = self
- def get_file_path(self, name: str) -> str:
- return os.path.join(self.dir, name)
-
- def process(self):
- # ⚠️ IMPORTANT NOTE: All methods called here are expected to be defined as abstract methods in the base classes, if not there is an issue with the engine implementation.
-
- # Kinda like in css, we have a z-index of moviepy clips (any). Then the engines append some clips to this, and we render it all with index 0 below, and index 9 at the top.
+ # Kinda like in css, we have a z-index of moviepy clips (any). Then the engines append some clips to this,
+ # and we render it all with index 0 below, and index 9 at the top.
self.index_0 = []
self.index_1 = []
self.index_2 = []
@@ -100,23 +105,45 @@ class GenerationContext:
self.index_7 = []
self.index_8 = []
self.index_9 = []
+
+ self.audio = []
+
self.credits = "Generated by AI"
+ def setup_dir(self):
+ self.dir = f"output/{time.time()}"
+ os.makedirs(self.dir)
+
+ def get_file_path(self, name: str) -> str:
+ return os.path.join(self.dir, name)
+
+ def process(self):
+ # ⚠️ IMPORTANT NOTE: All methods called here are expected to be defined as abstract methods in the base
+ # classes, if not there is an issue with the engine implementation.
+
self.progress(0.1, "Loading settings...")
self.setup_dir()
- self.settingsengine.load()
+ if not isinstance(self.settingsengine, engines.NoneEngine):
+ self.settingsengine.load()
self.progress(0.2, "Generating script...")
- self.scriptengine.generate()
+ if not isinstance(self.powerfulllmengine, engines.NoneEngine):
+ self.scriptengine.generate()
- self.progress(0.3, "Generating synthtetizing voice...")
- self.ttsengine.synthesize(self.script, self.get_file_path("tts.wav"))
- self.duration: float # for type hinting
+ self.progress(0.3, "Synthesizing voice...")
+ if not isinstance(self.ttsengine, engines.NoneEngine):
+ self.ttsengine.synthesize(self.script, self.get_file_path("tts.wav"))
+ self.duration: float # for type hinting
+ self.audio.append(mp.AudioFileClip(self.get_file_path("tts.wav")))
if not isinstance(self.backgroundengine, engines.NoneEngine):
self.progress(0.4, "Generating background...")
self.backgroundengine.get_background()
+ if not isinstance(self.audiobackgroundengine, engines.NoneEngine):
+ self.progress(0.45, "Generating audio background...")
+ self.audiobackgroundengine.get_background()
+
self.assetsengine = [
engine
for engine in self.assetsengine
@@ -129,10 +156,6 @@ class GenerationContext:
if not isinstance(self.captioningengine, engines.NoneEngine):
self.progress(0.6, "Generating captions...")
self.captioningengine.get_captions()
- else:
- self.captions = []
-
- # add any other processing steps here
# we render to a file called final.mp4
self.progress(0.7, "Rendering video...")
@@ -149,18 +172,22 @@ class GenerationContext:
*self.index_9,
]
clip = mp.CompositeVideoClip(clips, size=(self.width, self.height))
+ audio = mp.CompositeAudioClip(self.audio)
clip.set_duration(self.duration)
- audio = mp.AudioFileClip(self.get_file_path("tts.wav"))
clip: mp.CompositeVideoClip = clip.set_audio(audio)
- clip.write_videofile(self.get_file_path("final.mp4"), fps=60, threads=4)
+ clip.write_videofile(self.get_file_path("final.mp4"), fps=60, threads=4, codec="h264_nvenc")
self.progress(0.8, "Generating metadata...")
self.metadataengine.get_metadata()
+ self.description = self.description + "\n" + self.credits
self.progress(0.9, "Uploading video...")
for engine in self.uploadengine:
- engine.upload()
-
+ try:
+ engine.upload()
+ except Exception as e:
+ print(e)
+ gradio.Warning(f"{engine.name} failed to upload the video.")
self.progress(0.99, "Storing in database...")
self.store_in_db()
self.progress(1, "Done!")
diff --git a/src/engines/AssetsEngine/GoogleAssetsEngine.py b/src/engines/AssetsEngine/GoogleAssetsEngine.py
index 41b55f5..48cf04e 100644
--- a/src/engines/AssetsEngine/GoogleAssetsEngine.py
+++ b/src/engines/AssetsEngine/GoogleAssetsEngine.py
@@ -49,16 +49,18 @@ class GoogleAssetsEngine(BaseAssetsEngine):
"num": 1,
}
os.makedirs("temp", exist_ok=True)
- self.google.search(
- search_params=_search_params,
- path_to_dir="./temp/",
- custom_image_name="temp",
- )
- # we find the file called temp. extension
- filename = [f for f in os.listdir("./temp/") if f.startswith("temp.")][0]
- img = mp.ImageClip(f"./temp/{filename}")
- # delete the temp folder
- shutil.rmtree("temp")
+ try:
+ self.google.search(
+ search_params=_search_params,
+ path_to_dir="./temp/",
+ custom_image_name="temp",
+ )
+ # we find the file called temp. extension
+ filename = [f for f in os.listdir("./temp/") if f.startswith("temp.")][0]
+ img = mp.ImageClip(f"./temp/{filename}")
+ # delete the temp folder
+ finally:
+ shutil.rmtree("temp")
img: mp.ImageClip = img.set_duration(end - start)
img: mp.ImageClip = img.set_start(start)
diff --git a/src/engines/AudioBackgroundEngine/BaseAudioBackgroundEngine.py b/src/engines/AudioBackgroundEngine/BaseAudioBackgroundEngine.py
new file mode 100644
index 0000000..ef26ef7
--- /dev/null
+++ b/src/engines/AudioBackgroundEngine/BaseAudioBackgroundEngine.py
@@ -0,0 +1,9 @@
+from abc import abstractmethod
+
+from ..BaseEngine import BaseEngine
+
+
+class BaseAudioBackgroundEngine(BaseEngine):
+ @abstractmethod
+ def get_background(self) -> None:
+ ...
diff --git a/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py b/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py
new file mode 100644
index 0000000..d3b305c
--- /dev/null
+++ b/src/engines/AudioBackgroundEngine/MusicAudioBackgroundEngine.py
@@ -0,0 +1,88 @@
+import os
+import random
+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
+
+from . import BaseAudioBackgroundEngine
+
+
+class MusicAudioBackgroundEngine(BaseAudioBackgroundEngine):
+ name = "Music Audio Background Engine"
+ description = "A basic background engine to set the background audio to a music track."
+ num_options = 1
+
+ def __init__(self, options: list[str]):
+ assets = self.get_assets(type="bcg_music")
+ self.background_audio = [asset for asset in assets if asset.data["name"] == options[0]][0]
+ super().__init__()
+
+ @classmethod
+ def get_options(cls) -> list:
+ assets = cls.get_assets(type="bcg_music")
+ choices = (
+ [asset.data["name"] for asset in assets]
+ if len(assets) > 0
+ else ["No audios available"]
+ )
+
+ return [
+ gr.Dropdown(
+ choices=choices,
+ label="Background Music",
+ value=choices[0] if len(assets) > 0 else "No audios available",
+ type="value",
+ )
+ ]
+
+ def get_background(self):
+ 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)
+ # 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)
+ # finally we add a new fade OUT only to the audio
+ audio: mp.AudioFileClip = audio.fx(audio_fadeout, 1)
+ # change volume to 0.5
+ audio: mp.AudioFileClip = audio.volumex(0.5)
+ self.ctx.audio.append(audio)
+
+ @classmethod
+ def get_settings(cls):
+ def add_file(fp: str, name: str, credits: str):
+ if name == "":
+ raise ValueError("Name cannot be empty.")
+ new_fp = f"local/assets/audios/{time.time()}{os.path.splitext(fp)[1]}"
+ shutil.move(fp, new_fp)
+ cls.add_asset(
+ path=new_fp,
+ metadata={"name": name, "credits": credits},
+ type="bcg_music",
+ )
+ gr.Info("Video added successfully.")
+
+ with gr.Column() as add_asset_inputs:
+ add_asset_name = gr.Textbox(label="Name of the audio", value="")
+ add_asset_credits = gr.Textbox(label="Credits", value="")
+ add_asset_input = gr.File(
+ file_count="single",
+ file_types=["audio"],
+ type="filepath",
+ )
+ with gr.Column() as add_asset_button:
+ add_asset_button = gr.Button(value="Add Audio")
+ add_asset_button.click(
+ add_file,
+ inputs=[add_asset_input, add_asset_name, add_asset_credits],
+ outputs=[],
+ )
diff --git a/src/engines/AudioBackgroundEngine/__init__.py b/src/engines/AudioBackgroundEngine/__init__.py
new file mode 100644
index 0000000..85949e6
--- /dev/null
+++ b/src/engines/AudioBackgroundEngine/__init__.py
@@ -0,0 +1,2 @@
+from .BaseAudioBackgroundEngine import BaseAudioBackgroundEngine
+from .MusicAudioBackgroundEngine import MusicAudioBackgroundEngine
diff --git a/src/engines/BackgroundEngine/BaseBackgroundEngine.py b/src/engines/BackgroundEngine/BaseBackgroundEngine.py
index 6ef6a74..3e51891 100644
--- a/src/engines/BackgroundEngine/BaseBackgroundEngine.py
+++ b/src/engines/BackgroundEngine/BaseBackgroundEngine.py
@@ -1,6 +1,6 @@
from abc import abstractmethod
-from ..BaseEngine import BaseEngine
+from src.engines.BaseEngine import BaseEngine
class BaseBackgroundEngine(BaseEngine):
diff --git a/src/engines/BaseEngine.py b/src/engines/BaseEngine.py
index 7275312..586758c 100644
--- a/src/engines/BaseEngine.py
+++ b/src/engines/BaseEngine.py
@@ -7,7 +7,6 @@ from ..chore import GenerationContext
from ..models import SessionLocal, File, Setting
-# noinspection PyTypeChecker
class BaseEngine(ABC):
num_options: int
name: str
@@ -15,7 +14,6 @@ class BaseEngine(ABC):
def __init__(self):
self.ctx: GenerationContext # This is for type hinting only
- pass
@classmethod
@abstractmethod
diff --git a/src/engines/CaptioningEngine/SimpleCaptioningEngine.py b/src/engines/CaptioningEngine/SimpleCaptioningEngine.py
index 2d0cf65..104df2e 100644
--- a/src/engines/CaptioningEngine/SimpleCaptioningEngine.py
+++ b/src/engines/CaptioningEngine/SimpleCaptioningEngine.py
@@ -1,4 +1,6 @@
import gradio as gr
+import os
+import shutil
from moviepy.editor import TextClip
from . import BaseCaptioningEngine
@@ -80,6 +82,13 @@ class SimpleCaptioningEngine(BaseCaptioningEngine):
self.ctx.index_7.extend(clips)
+ @classmethod
+ def get_settings(cls):
+ gr.Markdown(
+ "To add a custom font, simply install the font on your system, restart the server, and input the exact "
+ "file name (without the path) in the dropdown."
+ )
+
@classmethod
def get_options(cls) -> list:
with gr.Column() as font_options:
@@ -87,7 +96,8 @@ class SimpleCaptioningEngine(BaseCaptioningEngine):
font = gr.Dropdown(
label="Font",
choices=TextClip.list("font"),
- value="Comic-Sans-MS",
+ value="Comic-Sans-MS-Bold",
+ allow_custom_value=True, # Allow custom font
)
font_size = gr.Number(
label="Font Size",
diff --git a/src/engines/NoneEngine.py b/src/engines/NoneEngine.py
index 1f304da..b7fa53e 100644
--- a/src/engines/NoneEngine.py
+++ b/src/engines/NoneEngine.py
@@ -2,13 +2,28 @@ from . import BaseEngine
class NoneEngine(BaseEngine):
- num_options = 0
- name = "None"
- description = "No engine selected"
+ """
+ This class represents a NoneEngine which is a subclass of BaseEngine.
+ It is used when no engine is selected. It does not have any options.
+ """
- def __init__(self):
+ num_options = 0 # The number of options available for this engine.
+ name = "None" # The name of the engine.
+ description = "No engine selected" # A brief description of the engine.
+
+ def __init__(self, *args, **kwargs):
+ """
+ Constructor method for the NoneEngine class. It calls the constructor of the BaseEngine class.
+ """
super().__init__()
@classmethod
def get_options(cls):
- return []
+ """
+ This class method returns the options available for the NoneEngine.
+ Since NoneEngine does not have any options, it returns an empty list.
+
+ Returns:
+ list: An empty list as there are no options for NoneEngine.
+ """
+ return []
\ No newline at end of file
diff --git a/src/engines/ScriptEngine/ScientificFactsScriptEngine.py b/src/engines/ScriptEngine/ScientificFactsScriptEngine.py
new file mode 100644
index 0000000..0446258
--- /dev/null
+++ b/src/engines/ScriptEngine/ScientificFactsScriptEngine.py
@@ -0,0 +1,37 @@
+import os
+
+import gradio as gr
+
+from .BaseScriptEngine import BaseScriptEngine
+from ...utils.prompting import get_prompt
+
+
+class ScientificFactsScriptEngine(BaseScriptEngine):
+ name = "Scientific facts Thoughts"
+ description = "Generate a scientific facts script."
+ num_options = 1
+
+ def __init__(self, options: list[list | tuple | str | int | float | bool | None]):
+ self.n_sentences = options[0]
+ super().__init__()
+
+ def generate(self):
+ sys_prompt, chat_prompt = get_prompt(
+ "scientific_facts",
+ location=os.path.join(
+ os.path.dirname(os.path.abspath(__file__)), "prompts"
+ ),
+ )
+ sys_prompt = sys_prompt.format(n_sentences=self.n_sentences)
+ chat_prompt = chat_prompt.format(n_sentences=self.n_sentences)
+ self.ctx.script = self.ctx.powerfulllmengine.generate(
+ system_prompt=sys_prompt,
+ chat_prompt=chat_prompt,
+ max_tokens=20 * self.n_sentences,
+ temperature=1.3,
+ json_mode=False,
+ ).strip()
+
+ @classmethod
+ def get_options(cls) -> list:
+ return [gr.Number(label="Number of sentences", value=5, minimum=1)]
diff --git a/src/engines/ScriptEngine/prompts/scientific_facts.yaml b/src/engines/ScriptEngine/prompts/scientific_facts.yaml
new file mode 100644
index 0000000..c7dfee2
--- /dev/null
+++ b/src/engines/ScriptEngine/prompts/scientific_facts.yaml
@@ -0,0 +1,19 @@
+system: |-
+ You are an expert content writer of a YouTube shorts channel. You specialize in scientific facts shorts.
+ Your shorts are {n_sentences} sentences long. This is VERY IMPORTANT, MAKE SURE TO RESPECT THIS LENGTH.
+ They are extremely captivating, and original.
+ You need to follow the following guidelines:
+ - **Hook the Viewer:** Start with a compelling question, fact, or scenario to grab attention immediately. Your fact can also be a bit wierd or shocking (not really shocking, but you get the point), so that the viewer wants to know the actual truth.
+ Specifically, you can start with something that isn't completely correct, then when you continue the actual explanation unfolds, in order to make the first few words more attractive.
+ - **Keep it Short and Sweet:** Deliver your content concisely and rapidly to match the platform's fast-paced nature.
+ - **Tap into Relatability or Curiosity:** Make your content relatable or introduce surprising elements to spark curiosity.
+ - **Maintain a Conversational Tone:** Use conversational language to make your content more accessible and engaging.
+ - **Use Visual Imagery:** Describe concepts in a way that invokes visual imagery, enhancing engagement.
+ - **Include a Call to Action:** End with a direct call to action to encourage viewer interaction if applicable.
+ You are now tasked to produce the greatest short script for the user.
+ Start with a compelling information, fact, or scenario to grab attention IMMEDIATELY.
+ Keep it short, EXTREMELY interesting and original.
+ If it is appropriate, at the end, ask a question to the user, and end point blank.
+ YOU never respond with anything else that the video script, not even a hello.
+chat: |
+ Please give me a script. Make sure to keep it {n_sentences} sentences long, including any questions or calls to action.
\ No newline at end of file
diff --git a/src/engines/TTSEngine/BaseTTSEngine.py b/src/engines/TTSEngine/BaseTTSEngine.py
index f4cff54..e3955c4 100644
--- a/src/engines/TTSEngine/BaseTTSEngine.py
+++ b/src/engines/TTSEngine/BaseTTSEngine.py
@@ -49,7 +49,7 @@ class BaseTTSEngine(BaseEngine):
"""
device = "cuda" if is_available() else "cpu"
audio = wt.load_audio(path)
- model = wt.load_model("small", device=device)
+ model = wt.load_model("large-v3", device=device)
result = wt.transcribe(model=model, audio=audio)
results = [word for chunk in result["segments"] for word in chunk["words"]]
diff --git a/src/engines/UploadEngine/TikTokUploadEngine.py b/src/engines/UploadEngine/TikTokUploadEngine.py
index 8975312..6c72a16 100644
--- a/src/engines/UploadEngine/TikTokUploadEngine.py
+++ b/src/engines/UploadEngine/TikTokUploadEngine.py
@@ -36,11 +36,11 @@ class TikTokUploadEngine(BaseUploadEngine):
description = description.replace(word, "")
title = title.strip()
- description = description.strip().replace("\n", " ") # Newlines are not supported by this uploader
+ description = description.strip()
hashtags_str = " ".join(hashtags) + " " if hashtags else ""
failed = upload_video(
filename=self.ctx.get_file_path("final.mp4"),
- description=f"{title} {description} {hashtags_str}",
+ description=f"{title}\n{description} {hashtags_str}",
cookies_str=cookies,
browser="chrome",
comment=True, stitch=False, duet=False
diff --git a/src/engines/__init__.py b/src/engines/__init__.py
index ea2e006..262b522 100644
--- a/src/engines/__init__.py
+++ b/src/engines/__init__.py
@@ -7,6 +7,7 @@ from . import ScriptEngine
from . import SettingsEngine
from . import TTSEngine
from . import UploadEngine
+from . import AudioBackgroundEngine
from .BaseEngine import BaseEngine
from .NoneEngine import NoneEngine
@@ -49,7 +50,11 @@ ENGINES: dict[str, dict[str, bool | list[BaseEngine]]] = {
"multiple": True,
},
"BackgroundEngine": {
- "classes": [BackgroundEngine.VideoBackgroundEngine, NoneEngine],
+ "classes": [NoneEngine, BackgroundEngine.VideoBackgroundEngine],
+ "multiple": False,
+ },
+ "AudioBackgroundEngine": {
+ "classes": [NoneEngine, AudioBackgroundEngine.MusicAudioBackgroundEngine],
"multiple": False,
},
"MetadataEngine": {
diff --git a/ui/gradio_ui.py b/ui/gradio_ui.py
index 0f0439a..254ba5a 100644
--- a/ui/gradio_ui.py
+++ b/ui/gradio_ui.py
@@ -186,13 +186,13 @@ class GenerateUI:
output_title.render()
output_description.render()
open_folder.render()
- open_folder.click(lambda x: os.system(f"open {os.path.abspath(x)}") if os.name == "posix" else os.system(f"explorer {os.abspath(x)}"), inputs=output_path)
+ open_folder.click(lambda x: os.system(f"open {os.path.abspath(x)}") if os.name == "posix" else os.system(f"explorer {os.path.abspath(x)}"), inputs=output_path)
with gr.Column():
output_video.render()
return interface
- def run_generate_interface(self, progress=gr.Progress(), *args) -> list[gr.update]:
+ def run_generate_interface(self, progress=gr.Progress(track_tqdm=True), *args) -> list[gr.update]:
progress(0, desc="Loading engines... 🚀")
options = self.repack_options(*args)
arguments = {name.lower(): options[name] for name in ENGINES.keys()}