Skip to content

Commit

Permalink
adding voice dubbing
Browse files Browse the repository at this point in the history
  • Loading branch information
RayVentura committed Jul 22, 2023
1 parent c00de2a commit c0689b6
Show file tree
Hide file tree
Showing 26 changed files with 529 additions and 158 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ flagged/
.env
ShortGPT.egg-info
dist
build
build
setup.py
test.ipynb
Empty file.
10 changes: 8 additions & 2 deletions gui/asset_components.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import gradio as gr
from shortGPT.config.asset_db import AssetDatabase

from shortGPT.config.api_db import get_api_key
from shortGPT.api_utils.eleven_api import getVoices
AssetDatabase().sync_local_assets()
def getBackgroundVideoChoices():
asset_db = AssetDatabase()
Expand All @@ -14,10 +15,15 @@ def getBackgroundMusicChoices():
choices = list(df.loc['background music' == df['type']]['name'])[:20]
return choices

def getElevenlabsVoices():
api_key = get_api_key("ELEVEN LABS")
voices = list(reversed(getVoices(api_key).keys()))
return voices

background_video_checkbox = gr.CheckboxGroup(choices=getBackgroundVideoChoices(), interactive=True, label="Choose background video")
background_music_checkbox = gr.CheckboxGroup(choices=getBackgroundMusicChoices(), interactive=True, label="Choose background music")

voiceChoice = gr.Radio(getElevenlabsVoices(), label="Elevenlabs voice", value="Antoni", interactive=True)
voiceChoiceTranslation = gr.Radio(getElevenlabsVoices(), label="Elevenlabs voice", value="Antoni", interactive=True)
import os, platform, subprocess

def start_file(path):
Expand Down
6 changes: 4 additions & 2 deletions gui/config_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import time
from shortGPT.config.api_db import get_api_key, set_api_key
from shortGPT.api_utils.eleven_api import getCharactersFromKey
from gui.short_automation_ui import voiceChoice, getElevenlabsVoices
from gui.asset_components import voiceChoice, voiceChoiceTranslation, getElevenlabsVoices
def onShow(button_text):
if button_text == "Show":
return gr.Textbox.update(type="text"), gr.Button.update(value="Hide")
Expand All @@ -25,11 +25,13 @@ def saveKeys(openai_key, eleven_key, pexels_key):
return gr.Textbox.update(value=openai_key),\
gr.Textbox.update(value=eleven_key),\
gr.Textbox.update(value=pexels_key),\
gr.Radio.update(choices=new_eleven_voices),\
gr.Radio.update(choices=new_eleven_voices)

return gr.Textbox.update(value=openai_key),\
gr.Textbox.update(value=eleven_key),\
gr.Textbox.update(value=pexels_key),\
gr.Radio.update(visible=True),\
gr.Radio.update(visible=True)

def getElevenRemaining(key):
Expand Down Expand Up @@ -60,7 +62,7 @@ def create_config_ui():
def back_to_normal():
time.sleep(3)
return gr.Button.update(value="save")
save_button.click(verify_eleven_key, [eleven_labs_textbox, eleven_characters_remaining], [eleven_characters_remaining]).success(saveKeys, [openai_textbox, eleven_labs_textbox, pexels_textbox], [openai_textbox, eleven_labs_textbox, pexels_textbox, voiceChoice])
save_button.click(verify_eleven_key, [eleven_labs_textbox, eleven_characters_remaining], [eleven_characters_remaining]).success(saveKeys, [openai_textbox, eleven_labs_textbox, pexels_textbox], [openai_textbox, eleven_labs_textbox, pexels_textbox, voiceChoice, voiceChoiceTranslation])
save_button.click(lambda _ : gr.Button.update(value="Keys Saved !"), [], [save_button])
save_button.click(back_to_normal, [], [save_button])
return config_ui
11 changes: 8 additions & 3 deletions gui/content_automation_ui.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import gradio as gr

from gui.video_translation_ui import create_video_translation_ui

ERROR_TEMPLATE = """<div style='text-align: center; background: #f2dede; color: #a94442; padding: 20px; border-radius: 5px; margin: 10px;'>
<h2 style='margin: 0;'>ERROR : {error_message}</h2>
<p style='margin: 10px 0;'>Traceback Info : {stack_trace}</p>
Expand All @@ -15,8 +17,11 @@ def create_content_automation(shortGPTUI: gr.Blocks):
with gr.Tab("Content Automation") as content_automation_ui:
gr.Markdown("# 🏆 Content Automation 🚀")
gr.Markdown("## Choose your desired automation task.")
choice = gr.Radio([ '🎬 Automate the creation of shorts', '🎞️ Automate a video with stock assets'], label="Choose an option")
choice = gr.Radio([ '🎬 Automate the creation of shorts', '🎞️ Automate a video with stock assets', '📹 Automate video translation'], label="Choose an option")
video_automation_ui = create_video_automation_ui(shortGPTUI)
short_automation_ui = create_short_automation_ui(shortGPTUI)
choice.change(lambda x: (gr.update(visible= x == choice.choices[1]), gr.update(visible= x == choice.choices[0])), [choice], [video_automation_ui, short_automation_ui])
return content_automation_ui
video_translation_ui = create_video_translation_ui(shortGPTUI)
choice.change(lambda x: (gr.update(visible= x == choice.choices[1]), gr.update(visible= x == choice.choices[0]), gr.update(visible= x == choice.choices[2])), [choice], [video_automation_ui, short_automation_ui, video_translation_ui])
return content_automation_ui

# video_translation_ui = create_video_translation_ui(shortGPTUI)
1 change: 1 addition & 0 deletions gui/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from gui.config_ui import create_config_ui
from gui.asset_library_ui import create_asset_library_ui
from gui.content_automation_ui import create_content_automation
from gui.video_translation_ui import create_video_translation_ui
max_choices = 20
ui_asset_dataframe = gr.Dataframe(interactive=False)
LOGO_PATH = "http://localhost:31415/file=public/logo.png"
Expand Down
12 changes: 2 additions & 10 deletions gui/short_automation_ui.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import traceback
import gradio as gr
from gui.asset_components import background_video_checkbox, background_music_checkbox, start_file
from gui.asset_components import background_video_checkbox, background_music_checkbox, voiceChoice, start_file
from shortGPT.config.api_db import get_api_key
from shortGPT.engine.reddit_short_engine import RedditShortEngine, Language
from shortGPT.engine.facts_short_engine import FactsShortEngine
from shortGPT.api_utils.eleven_api import getVoices
import time
language_choices = [lang.value.upper() for lang in Language]
import gradio as gr
Expand All @@ -24,13 +23,6 @@
border-radius: 5px; cursor: pointer; text-decoration: none;'>Get Help on Discord</a>
</div>"""

def getElevenlabsVoices():
api_key = get_api_key("ELEVEN LABS")
voices = list(reversed(getVoices(api_key).keys()))
return voices

voiceChoice = gr.Radio(getElevenlabsVoices(), label="Elevenlabs voice", value="Antoni", interactive=True)

def create_short_automation_ui(shortGptUI: gr.Blocks):
def create_short(numShorts,
short_type,
Expand Down Expand Up @@ -66,7 +58,7 @@ def logger(prog_str):
progress(progress_counter / (num_steps * numShorts),f"Making short {i+1}/{numShorts} - {prog_str}")
shortEngine.set_logger(logger)

for step_num, step_info in shortEngine.makeShort():
for step_num, step_info in shortEngine.makeContent():
progress(progress_counter / (num_steps * numShorts), f"Making short {i+1}/{numShorts} - {step_info}")
progress_counter += 1

Expand Down
2 changes: 1 addition & 1 deletion gui/video_automation_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def makeVideo(script, language, isVertical, progress):
def logger(prog_str):
progress(progress_counter / (num_steps),f"Creating video - {progress_counter} - {prog_str}")
shortEngine.set_logger(logger)
for step_num, step_info in shortEngine.makeShort():
for step_num, step_info in shortEngine.makeContent():
progress(progress_counter / (num_steps), f"Creating video - {step_info}")
progress_counter += 1

Expand Down
128 changes: 128 additions & 0 deletions gui/video_translation_ui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import traceback
import gradio as gr
from gui.asset_components import voiceChoiceTranslation, start_file
from shortGPT.engine.content_translation_engine import ContentTranslationEngine, Language
import time
language_choices = [lang.value.upper() for lang in Language]
import gradio as gr
import os
import time

ERROR_TEMPLATE = """
<div style='text-align: center; background: #9fcbc3; color: #3f4039;
padding: 20px; border-radius: 5px; margin: 10px;'>
<h2 style='margin: 0;'>ERROR | {error_message}</h2>
<p style='margin: 10px 0;'>Traceback Info : {stack_trace}</p>
<p style='margin: 10px 0;'>If the problem persists, don't hesitate to
contact our support. We're here to assist you.</p>
<a href='https://discord.gg/qn2WJaRH' target='_blank'
style='background: #3f4039; color: #fff; border: none; padding: 10px 20px;
border-radius: 5px; cursor: pointer; text-decoration: none;'>Get Help on Discord</a>
</div>"""


def create_video_translation_ui(shortGptUI: gr.Blocks):
def translate_video(
videoType,
yt_link,
video_path,
target_language,
use_captions: bool,
voice: str,
progress=gr.Progress()):
language = Language(target_language.lower())
embedHTML = '<div style="display: flex; overflow-x: auto; gap: 20px;">'
progress_counter = 0
try:
content_translation_engine = ContentTranslationEngine(src_url=yt_link if videoType=="Youtube link" else video_path, target_language=language, use_captions=use_captions, voice_name=voice )
num_steps = content_translation_engine.get_total_steps()
def logger(prog_str):
progress(progress_counter / (num_steps),f"Translating your video - {prog_str}")
content_translation_engine.set_logger(logger)

for step_num, step_info in content_translation_engine.makeContent():
progress(progress_counter / (num_steps),f"Translating your video - {step_info}")
progress_counter += 1

video_path = content_translation_engine.get_video_output_path()
current_url = shortGptUI.share_url+"/" if shortGptUI.share else shortGptUI.local_url
file_url_path = f"{current_url}file={video_path}"
file_name = video_path.split("/")[-1].split("\\")[-1]
embedHTML += f'''
<div style="display: flex; flex-direction: column; align-items: center;">
<video width="{500}" style="max-height: 100%;" controls>
<source src="{file_url_path}" type="video/mp4">
Your browser does not support the video tag.
</video>
<a href="{file_url_path}" download="{file_name}" style="margin-top: 10px;">
<button style="font-size: 1em; padding: 10px; border: none; cursor: pointer; color: white; background: #007bff;">Download Video</button>
</a>
</div>'''
return embedHTML + '</div>', gr.Button.update(visible=True), gr.update(visible=False)

except Exception as e:
traceback_str = ''.join(traceback.format_tb(e.__traceback__))
error_name = type(e).__name__.capitalize()+ " : " +f"{e.args[0]}"
print("Error", traceback_str)
return embedHTML + '</div>', gr.Button.update(visible=True), gr.update(value=ERROR_TEMPLATE.format(error_message=error_name, stack_trace=traceback_str), visible=True)




with gr.Row(visible=False) as video_translation_ui:
with gr.Column():
videoType = gr.Radio(["Youtube link", "Video file"], label="Input your video", value="Video file", interactive=True)
video_path = gr.Video(source="upload", interactive=True, width=533.33, height=300)
yt_link = gr.Textbox(label="Youtube link (https://youtube.com/xyz): ", interactive=True, visible=False)
videoType.change(lambda x: (gr.update(visible= x == "Video file"), gr.update(visible= x == "Youtube link")), [videoType], [video_path, yt_link] )
language = gr.Radio(language_choices, label="Target Language", value="SPANISH", interactive=True)
voiceChoiceTranslation.render()
useCaptions = gr.Checkbox(label="Caption video", value=False)

translateButton = gr.Button(label="Create Shorts")

generation_error = gr.HTML(visible=False)
video_folder = gr.Button("📁", visible=True)
file_name= "videos/2023-07-22_16-17-06 - translatedcontenttofrench.mp4"
file_url_path = f"http://127.0.0.1:31415/file={file_name}"
output = gr.HTML(f'''
<div style="display: flex; overflow-x: auto; gap: 20px;"><div style="display: flex; flex-direction: column; align-items: center;">
<video width="{500}" style="max-height: 100%;" controls>
<source src="{file_url_path}" type="video/mp4">
Your browser does not support the video tag.
</video>
<a href="{file_url_path}" style="margin-top: 10px;">
<button style="font-size: 1em; padding: 10px; border: none; cursor: pointer; color: white; background: #007bff;">Download Video</button>
</a>
</div></div>''')

video_folder.click(lambda _: start_file(os.path.abspath("videos/")))
translateButton.click(inspect_create_inputs, inputs=[videoType, video_path, yt_link, ], outputs=[generation_error]).success(translate_video, inputs=[
videoType, yt_link, video_path, language, useCaptions, voiceChoiceTranslation
], outputs=[output, video_folder, generation_error])
return video_translation_ui



def inspect_create_inputs(videoType, video_path, yt_link):
supported_extensions = ['.mp4', '.avi', '.mov'] # Add more supported video extensions if needed
print(videoType, video_path, yt_link)
if videoType == "Youtube link":
if not yt_link.startswith("https://youtube.com/") and not yt_link.startswith("https://www.youtube.com/"):
raise gr.Error('Invalid YouTube URL. Please provide a valid URL. Link example: https://www.youtube.com/watch?v=dQw4w9WgXcQ')
else:
if not video_path or not os.path.exists(video_path):
raise gr.Error('You must drag and drop a valid video file.')

file_ext = os.path.splitext(video_path)[-1].lower()
if file_ext not in supported_extensions:
raise gr.Error('Invalid video file. Supported video file extensions are: {}'.format(', '.join(supported_extensions)))
return gr.update(visible=False)

def update_progress(progress, progress_counter, num_steps, num_shorts, stop_event):
start_time = time.time()
while not stop_event.is_set():
elapsed_time = time.time() - start_time
dynamic = int(3649 * elapsed_time / 600)
progress(progress_counter / (num_steps * num_shorts), f"Rendering progress - {dynamic}/3649")
time.sleep(0.1) # update every 0.1 second
2 changes: 1 addition & 1 deletion shortGPT/api_utils/eleven_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def generateVoice(text, character, fileName, stability=0.2, clarity=0.1, api_key
return fileName
else:
message = response.text
print(f'Error in response, {response.status_code} , message: {message}')
raise Exception(f'Error in response, {response.status_code} , message: {message}')
return ""

# print(getCharactersFromKey(''))
50 changes: 14 additions & 36 deletions shortGPT/audio/audio_duration.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import yt_dlp
import subprocess
import json

from shortGPT.editing_utils.handle_videos import getYoutubeVideoLink

def get_duration_yt_dlp(url):
ydl_opts = {
Expand Down Expand Up @@ -45,24 +45,21 @@ def get_duration_ffprobe(signed_url):
def getAssetDuration(url, isVideo=True):
if("youtube.com" in url):
if not isVideo:
return getYoutubeAudioLink(url)
url, _ = getYoutubeAudioLink(url)
else:
return getYoutubeVideoLink(url)

#Audio/Video is from some cloud storage provider. Link must be public.
else:
#Trying two different method to get the duration of the video / audio
duration, err_ffprobe = get_duration_ffprobe(url)
if duration is not None:
return url, duration
url, _ = getYoutubeVideoLink(url)
#Trying two different method to get the duration of the video / audio
duration, err_ffprobe = get_duration_ffprobe(url)
if duration is not None:
return url, duration

duration, err_yt_dlp = get_duration_yt_dlp(url)
if duration is not None:
return url, duration
print(err_yt_dlp)
print(err_ffprobe)
print(f"The url/path {url} does not point to a video/ audio. Impossible to extract its duration")
return url, None
duration, err_yt_dlp = get_duration_yt_dlp(url)
if duration is not None:
return url, duration
print(err_yt_dlp)
print(err_ffprobe)
print(f"The url/path {url} does not point to a video/ audio. Impossible to extract its duration")
return url, None


def getYoutubeAudioLink(url):
Expand All @@ -83,22 +80,3 @@ def getYoutubeAudioLink(url):
except Exception as e:
print("Failed getting audio link from the following video/url", e.args[0])
return None

def getYoutubeVideoLink(url):
ydl_opts = {
"quiet": True,
"no_warnings": True,
"no_color": True,
"no_call_home": True,
"no_check_certificate": True,
"format": "bestvideo[height<=1080]"
}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
dictMeta = ydl.extract_info(
url,
download=False)
return dictMeta['url'], dictMeta['duration']
except Exception as e:
print("Failed getting video link from the following video/url", e.args[0])
return None, None
14 changes: 8 additions & 6 deletions shortGPT/audio/audio_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ def downloadYoutubeAudio(url, outputFile):
print("Failed downloading audio from the following video/url", e.args[0])
return None

def speedUpAudio(tempAudioPath, outputFile, expected_chars_per_sec=CONST_CHARS_PER_SEC): # Speeding up the audio to make it under 60secs, otherwise the output video is not considered as a short.
def speedUpAudio(tempAudioPath, outputFile, expected_duration=None): # Speeding up the audio to make it under 60secs, otherwise the output video is not considered as a short.
tempAudioPath, duration = getAssetDuration(tempAudioPath, False)
if(duration > 57):
subprocess.run(['ffmpeg', '-i', tempAudioPath, '-af', f'atempo={(duration/57):.5f}', outputFile])
if not expected_duration:
if(duration > 57):
subprocess.run(['ffmpeg', '-i', tempAudioPath, '-af', f'atempo={(duration/57):.5f}', outputFile])
else:
subprocess.run(['ffmpeg', '-i', tempAudioPath, outputFile])
else:
subprocess.run(['ffmpeg', '-i', tempAudioPath, outputFile])
subprocess.run(['ffmpeg', '-i', tempAudioPath, '-af', f'atempo={(duration/expected_duration):.5f}', outputFile])
if(os.path.exists(outputFile)):
return outputFile
return ""
return outputFile

def ChunkForAudio(alltext, chunk_size=2500):
alltext_list = alltext.split('.')
Expand Down
4 changes: 2 additions & 2 deletions shortGPT/audio/eleven_voice_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from shortGPT.audio.voice_module import VoiceModule

class ElevenLabsVoiceModule(VoiceModule):
def __init__(self, api_key, voiceName):
def __init__(self, api_key, voiceName, checkElevenCredits):
self.api_key = api_key
self.voiceName = voiceName
self.remaining_credits = None
self.update_usage()
if self.get_remaining_characters() < 1200:
if checkElevenCredits and self.get_remaining_characters() < 1200:
raise Exception(f"Your ElevenLabs API KEY doesn't have enough credits ({self.remaining_credits} character remaining). Minimum required: 1200 characters (equivalent to a 45sec short)")
super().__init__()

Expand Down
Loading

0 comments on commit c0689b6

Please sign in to comment.