I have no time to review my commit

This commit is contained in:
2024-02-27 14:24:25 +01:00
parent c70872a0e5
commit 166eff8a8a
23 changed files with 400 additions and 53 deletions

82
.gitignore vendored
View File

@@ -145,11 +145,83 @@ dmypy.json
cython_debug/ cython_debug/
# PyCharm # PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# 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. # User-specific stuff
#.idea/ .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/ output/
local/ local/

8
.idea/.gitignore generated vendored Normal file
View File

@@ -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

12
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="db" uuid="18885715-8b98-4b45-8dd2-b39a20d145e1">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:C:\Users\jerem\D\09._AI_projects\viralautomator\local\database\db.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (viralautomator)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (viralautomator)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/viralautomator.iml" filepath="$PROJECT_DIR$/.idea/viralautomator.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

16
.idea/viralautomator.iml generated Normal file
View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/local/database" />
<excludeFolder url="file://$MODULE_DIR$/output" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (viralautomator)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
</component>
</module>

View File

@@ -2,6 +2,7 @@ import os
import time import time
from datetime import datetime from datetime import datetime
import gradio
import moviepy.editor as mp import moviepy.editor as mp
from .. import engines from .. import engines
@@ -35,8 +36,16 @@ class GenerationContext:
backgroundengine, backgroundengine,
metadataengine, metadataengine,
uploadengine, uploadengine,
audiobackgroundengine,
progress, progress,
) -> None: ) -> 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.progress = progress
self.powerfulllmengine: engines.LLMEngine.BaseLLMEngine = powerfulllmengine[0] self.powerfulllmengine: engines.LLMEngine.BaseLLMEngine = powerfulllmengine[0]
@@ -79,17 +88,13 @@ class GenerationContext:
for eng in self.uploadengine: for eng in self.uploadengine:
eng.ctx = self eng.ctx = self
def setup_dir(self): self.audiobackgroundengine: engines.AudioBackgroundEngine.BaseAudioBackgroundEngine = (
self.dir = f"output/{time.time()}" audiobackgroundengine[0]
os.makedirs(self.dir) )
self.audiobackgroundengine.ctx = self
def get_file_path(self, name: str) -> str: # Kinda like in css, we have a z-index of moviepy clips (any). Then the engines append some clips to this,
return os.path.join(self.dir, name) # and we render it all with index 0 below, and index 9 at the top.
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.
self.index_0 = [] self.index_0 = []
self.index_1 = [] self.index_1 = []
self.index_2 = [] self.index_2 = []
@@ -100,23 +105,45 @@ class GenerationContext:
self.index_7 = [] self.index_7 = []
self.index_8 = [] self.index_8 = []
self.index_9 = [] self.index_9 = []
self.audio = []
self.credits = "Generated by AI" 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.progress(0.1, "Loading settings...")
self.setup_dir() self.setup_dir()
self.settingsengine.load() if not isinstance(self.settingsengine, engines.NoneEngine):
self.settingsengine.load()
self.progress(0.2, "Generating script...") 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.progress(0.3, "Synthesizing voice...")
self.ttsengine.synthesize(self.script, self.get_file_path("tts.wav")) if not isinstance(self.ttsengine, engines.NoneEngine):
self.duration: float # for type hinting 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): if not isinstance(self.backgroundengine, engines.NoneEngine):
self.progress(0.4, "Generating background...") self.progress(0.4, "Generating background...")
self.backgroundengine.get_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 = [ self.assetsengine = [
engine engine
for engine in self.assetsengine for engine in self.assetsengine
@@ -129,10 +156,6 @@ class GenerationContext:
if not isinstance(self.captioningengine, engines.NoneEngine): if not isinstance(self.captioningengine, engines.NoneEngine):
self.progress(0.6, "Generating captions...") self.progress(0.6, "Generating captions...")
self.captioningengine.get_captions() self.captioningengine.get_captions()
else:
self.captions = []
# add any other processing steps here
# we render to a file called final.mp4 # we render to a file called final.mp4
self.progress(0.7, "Rendering video...") self.progress(0.7, "Rendering video...")
@@ -149,18 +172,22 @@ class GenerationContext:
*self.index_9, *self.index_9,
] ]
clip = mp.CompositeVideoClip(clips, size=(self.width, self.height)) clip = mp.CompositeVideoClip(clips, size=(self.width, self.height))
audio = mp.CompositeAudioClip(self.audio)
clip.set_duration(self.duration) clip.set_duration(self.duration)
audio = mp.AudioFileClip(self.get_file_path("tts.wav"))
clip: mp.CompositeVideoClip = clip.set_audio(audio) 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.progress(0.8, "Generating metadata...")
self.metadataengine.get_metadata() self.metadataengine.get_metadata()
self.description = self.description + "\n" + self.credits
self.progress(0.9, "Uploading video...") self.progress(0.9, "Uploading video...")
for engine in self.uploadengine: 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.progress(0.99, "Storing in database...")
self.store_in_db() self.store_in_db()
self.progress(1, "Done!") self.progress(1, "Done!")

View File

@@ -49,16 +49,18 @@ class GoogleAssetsEngine(BaseAssetsEngine):
"num": 1, "num": 1,
} }
os.makedirs("temp", exist_ok=True) os.makedirs("temp", exist_ok=True)
self.google.search( try:
search_params=_search_params, self.google.search(
path_to_dir="./temp/", search_params=_search_params,
custom_image_name="temp", 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] # we find the file called temp. extension
img = mp.ImageClip(f"./temp/{filename}") filename = [f for f in os.listdir("./temp/") if f.startswith("temp.")][0]
# delete the temp folder img = mp.ImageClip(f"./temp/{filename}")
shutil.rmtree("temp") # delete the temp folder
finally:
shutil.rmtree("temp")
img: mp.ImageClip = img.set_duration(end - start) img: mp.ImageClip = img.set_duration(end - start)
img: mp.ImageClip = img.set_start(start) img: mp.ImageClip = img.set_start(start)

View File

@@ -0,0 +1,9 @@
from abc import abstractmethod
from ..BaseEngine import BaseEngine
class BaseAudioBackgroundEngine(BaseEngine):
@abstractmethod
def get_background(self) -> None:
...

View File

@@ -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=[],
)

View File

@@ -0,0 +1,2 @@
from .BaseAudioBackgroundEngine import BaseAudioBackgroundEngine
from .MusicAudioBackgroundEngine import MusicAudioBackgroundEngine

View File

@@ -1,6 +1,6 @@
from abc import abstractmethod from abc import abstractmethod
from ..BaseEngine import BaseEngine from src.engines.BaseEngine import BaseEngine
class BaseBackgroundEngine(BaseEngine): class BaseBackgroundEngine(BaseEngine):

View File

@@ -7,7 +7,6 @@ from ..chore import GenerationContext
from ..models import SessionLocal, File, Setting from ..models import SessionLocal, File, Setting
# noinspection PyTypeChecker
class BaseEngine(ABC): class BaseEngine(ABC):
num_options: int num_options: int
name: str name: str
@@ -15,7 +14,6 @@ class BaseEngine(ABC):
def __init__(self): def __init__(self):
self.ctx: GenerationContext # This is for type hinting only self.ctx: GenerationContext # This is for type hinting only
pass
@classmethod @classmethod
@abstractmethod @abstractmethod

View File

@@ -1,4 +1,6 @@
import gradio as gr import gradio as gr
import os
import shutil
from moviepy.editor import TextClip from moviepy.editor import TextClip
from . import BaseCaptioningEngine from . import BaseCaptioningEngine
@@ -80,6 +82,13 @@ class SimpleCaptioningEngine(BaseCaptioningEngine):
self.ctx.index_7.extend(clips) 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 @classmethod
def get_options(cls) -> list: def get_options(cls) -> list:
with gr.Column() as font_options: with gr.Column() as font_options:
@@ -87,7 +96,8 @@ class SimpleCaptioningEngine(BaseCaptioningEngine):
font = gr.Dropdown( font = gr.Dropdown(
label="Font", label="Font",
choices=TextClip.list("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( font_size = gr.Number(
label="Font Size", label="Font Size",

View File

@@ -2,13 +2,28 @@ from . import BaseEngine
class NoneEngine(BaseEngine): class NoneEngine(BaseEngine):
num_options = 0 """
name = "None" This class represents a NoneEngine which is a subclass of BaseEngine.
description = "No engine selected" 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__() super().__init__()
@classmethod @classmethod
def get_options(cls): 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 []

View File

@@ -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)]

View File

@@ -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.

View File

@@ -49,7 +49,7 @@ class BaseTTSEngine(BaseEngine):
""" """
device = "cuda" if is_available() else "cpu" device = "cuda" if is_available() else "cpu"
audio = wt.load_audio(path) 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) result = wt.transcribe(model=model, audio=audio)
results = [word for chunk in result["segments"] for word in chunk["words"]] results = [word for chunk in result["segments"] for word in chunk["words"]]

View File

@@ -36,11 +36,11 @@ class TikTokUploadEngine(BaseUploadEngine):
description = description.replace(word, "") description = description.replace(word, "")
title = title.strip() 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 "" hashtags_str = " ".join(hashtags) + " " if hashtags else ""
failed = upload_video( failed = upload_video(
filename=self.ctx.get_file_path("final.mp4"), filename=self.ctx.get_file_path("final.mp4"),
description=f"{title} {description} {hashtags_str}", description=f"{title}\n{description} {hashtags_str}",
cookies_str=cookies, cookies_str=cookies,
browser="chrome", browser="chrome",
comment=True, stitch=False, duet=False comment=True, stitch=False, duet=False

View File

@@ -7,6 +7,7 @@ from . import ScriptEngine
from . import SettingsEngine from . import SettingsEngine
from . import TTSEngine from . import TTSEngine
from . import UploadEngine from . import UploadEngine
from . import AudioBackgroundEngine
from .BaseEngine import BaseEngine from .BaseEngine import BaseEngine
from .NoneEngine import NoneEngine from .NoneEngine import NoneEngine
@@ -49,7 +50,11 @@ ENGINES: dict[str, dict[str, bool | list[BaseEngine]]] = {
"multiple": True, "multiple": True,
}, },
"BackgroundEngine": { "BackgroundEngine": {
"classes": [BackgroundEngine.VideoBackgroundEngine, NoneEngine], "classes": [NoneEngine, BackgroundEngine.VideoBackgroundEngine],
"multiple": False,
},
"AudioBackgroundEngine": {
"classes": [NoneEngine, AudioBackgroundEngine.MusicAudioBackgroundEngine],
"multiple": False, "multiple": False,
}, },
"MetadataEngine": { "MetadataEngine": {

View File

@@ -186,13 +186,13 @@ class GenerateUI:
output_title.render() output_title.render()
output_description.render() output_description.render()
open_folder.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(): with gr.Column():
output_video.render() output_video.render()
return interface 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... 🚀") progress(0, desc="Loading engines... 🚀")
options = self.repack_options(*args) options = self.repack_options(*args)
arguments = {name.lower(): options[name] for name in ENGINES.keys()} arguments = {name.lower(): options[name] for name in ENGINES.keys()}