Shorts / shortGPT /engine /content_video_engine.py
HashScripts's picture
Upload 167 files
b293d47 verified
import datetime
import os
import re
import shutil
from shortGPT.api_utils.pexels_api import getBestVideo
from shortGPT.audio import audio_utils
from shortGPT.audio.audio_duration import get_asset_duration
from shortGPT.audio.voice_module import VoiceModule
from shortGPT.config.asset_db import AssetDatabase
from shortGPT.config.languages import Language
from shortGPT.editing_framework.editing_engine import (EditingEngine,
EditingStep)
from shortGPT.editing_utils import captions
from shortGPT.engine.abstract_content_engine import AbstractContentEngine
from shortGPT.gpt import gpt_editing, gpt_translate, gpt_yt
class ContentVideoEngine(AbstractContentEngine):
def __init__(self, voiceModule: VoiceModule, script: str, background_music_name="", id="",
watermark=None, isVerticalFormat=False, language: Language = Language.ENGLISH):
super().__init__(id, "general_video", language, voiceModule)
if not id:
if (watermark):
self._db_watermark = watermark
if background_music_name:
self._db_background_music_name = background_music_name
self._db_script = script
self._db_format_vertical = isVerticalFormat
self.stepDict = {
1: self._generateTempAudio,
2: self._speedUpAudio,
3: self._timeCaptions,
4: self._generateVideoSearchTerms,
5: self._generateVideoUrls,
6: self._chooseBackgroundMusic,
7: self._prepareBackgroundAssets,
8: self._prepareCustomAssets,
9: self._editAndRenderShort,
10: self._addMetadata
}
def _generateTempAudio(self):
if not self._db_script:
raise NotImplementedError("generateScript method must set self._db_script.")
if (self._db_temp_audio_path):
return
self.verifyParameters(text=self._db_script)
script = self._db_script
if (self._db_language != Language.ENGLISH.value):
self._db_translated_script = gpt_translate.translateContent(script, self._db_language)
script = self._db_translated_script
self._db_temp_audio_path = self.voiceModule.generate_voice(
script, self.dynamicAssetDir + "temp_audio_path.wav")
def _speedUpAudio(self):
if (self._db_audio_path):
return
self.verifyParameters(tempAudioPath=self._db_temp_audio_path)
# Since the video is not supposed to be a short( less than 60sec), there is no reason to speed it up
self._db_audio_path = self._db_temp_audio_path
return
self._db_audio_path = audio_utils.speedUpAudio(
self._db_temp_audio_path, self.dynamicAssetDir+"audio_voice.wav")
def _timeCaptions(self):
self.verifyParameters(audioPath=self._db_audio_path)
whisper_analysis = audio_utils.audioToText(self._db_audio_path)
max_len = 15
if not self._db_format_vertical:
max_len = 30
self._db_timed_captions = captions.getCaptionsWithTime(
whisper_analysis, maxCaptionSize=max_len)
def _generateVideoSearchTerms(self):
self.verifyParameters(captionsTimed=self._db_timed_captions)
# Returns a list of pairs of timing (t1,t2) + 3 search video queries, such as: [[t1,t2], [search_query_1, search_query_2, search_query_3]]
self._db_timed_video_searches = gpt_editing.getVideoSearchQueriesTimed(self._db_timed_captions)
def _generateVideoUrls(self):
timed_video_searches = self._db_timed_video_searches
self.verifyParameters(captionsTimed=timed_video_searches)
timed_video_urls = []
used_links = []
for (t1, t2), search_terms in timed_video_searches:
url = ""
for query in reversed(search_terms):
url = getBestVideo(query, orientation_landscape=not self._db_format_vertical, used_vids=used_links)
if url:
used_links.append(url.split('.hd')[0])
break
timed_video_urls.append([[t1, t2], url])
self._db_timed_video_urls = timed_video_urls
def _chooseBackgroundMusic(self):
if self._db_background_music_name:
self._db_background_music_url = AssetDatabase.get_asset_link(self._db_background_music_name)
def _prepareBackgroundAssets(self):
self.verifyParameters(voiceover_audio_url=self._db_audio_path)
if not self._db_voiceover_duration:
self.logger("Rendering short: (1/4) preparing voice asset...")
self._db_audio_path, self._db_voiceover_duration = get_asset_duration(
self._db_audio_path, isVideo=False)
def _prepareCustomAssets(self):
self.logger("Rendering short: (3/4) preparing custom assets...")
pass
def _editAndRenderShort(self):
self.verifyParameters(
voiceover_audio_url=self._db_audio_path)
outputPath = self.dynamicAssetDir+"rendered_video.mp4"
if not (os.path.exists(outputPath)):
self.logger("Rendering short: Starting automated editing...")
videoEditor = EditingEngine()
videoEditor.addEditingStep(EditingStep.ADD_VOICEOVER_AUDIO, {
'url': self._db_audio_path})
if (self._db_background_music_url):
videoEditor.addEditingStep(EditingStep.ADD_BACKGROUND_MUSIC, {'url': self._db_background_music_url,
'loop_background_music': self._db_voiceover_duration,
"volume_percentage": 0.08})
for (t1, t2), video_url in self._db_timed_video_urls:
videoEditor.addEditingStep(EditingStep.ADD_BACKGROUND_VIDEO, {'url': video_url,
'set_time_start': t1,
'set_time_end': t2})
if (self._db_format_vertical):
caption_type = EditingStep.ADD_CAPTION_SHORT_ARABIC if self._db_language == Language.ARABIC.value else EditingStep.ADD_CAPTION_SHORT
else:
caption_type = EditingStep.ADD_CAPTION_LANDSCAPE_ARABIC if self._db_language == Language.ARABIC.value else EditingStep.ADD_CAPTION_LANDSCAPE
for (t1, t2), text in self._db_timed_captions:
videoEditor.addEditingStep(caption_type, {'text': text.upper(),
'set_time_start': t1,
'set_time_end': t2})
videoEditor.renderVideo(outputPath, logger= self.logger if self.logger is not self.default_logger else None)
self._db_video_path = outputPath
def _addMetadata(self):
if not os.path.exists('videos/'):
os.makedirs('videos')
self._db_yt_title, self._db_yt_description = gpt_yt.generate_title_description_dict(self._db_script)
now = datetime.datetime.now()
date_str = now.strftime("%Y-%m-%d_%H-%M-%S")
newFileName = f"videos/{date_str} - " + \
re.sub(r"[^a-zA-Z0-9 '\n\.]", '', self._db_yt_title)
shutil.move(self._db_video_path, newFileName+".mp4")
with open(newFileName+".txt", "w", encoding="utf-8") as f:
f.write(
f"---Youtube title---\n{self._db_yt_title}\n---Youtube description---\n{self._db_yt_description}")
self._db_video_path = newFileName+".mp4"
self._db_ready_to_upload = True