diff --git a/TTS/TikTok.py b/TTS/TikTok.py index 198866b..9f5a3b4 100644 --- a/TTS/TikTok.py +++ b/TTS/TikTok.py @@ -71,7 +71,7 @@ class TikTok(BaseApiTTS): # TikTok Text-to-Speech Wrapper ) random_voice: bool = False uri_base: str = attrib( - default='https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke', + default='https://api16-normal-useast5.us.tiktokv.com/media/api/text/speech/invoke/', kw_only=True, ) max_chars = 300 @@ -91,11 +91,12 @@ class TikTok(BaseApiTTS): # TikTok Text-to-Speech Wrapper self, text: str, ): - return await self.client.post( - f'{self.uri_base}', - params={ - 'text_speaker': self.voice, - 'req_text': text, - 'speaker_map_type': 0, - } - ) + async with self.client.post( + f'{self.uri_base}', + params={ + 'text_speaker': self.voice, + 'req_text': text, + 'speaker_map_type': 0, + }) as results: + results = await results.json() + return results['data']['v_str'] diff --git a/TTS/engine_wrapper.py b/TTS/engine_wrapper.py index 0b97809..9b6600f 100644 --- a/TTS/engine_wrapper.py +++ b/TTS/engine_wrapper.py @@ -49,29 +49,31 @@ class TTSEngine: print_step('Saving Text to MP3 files...') - await self.call_tts('title', self.reddit_object['thread_title']) - async_tasks_offset = 1 + async_tasks_primary = list() + + async_tasks_primary.append(self.call_tts('title', self.reddit_object['thread_title'])) if self.reddit_object['thread_post'] and settings.config['settings']['storymode']: - await self.call_tts('posttext', self.reddit_object['thread_post']) - async_tasks_offset += 1 + async_tasks_primary.append(self.call_tts('posttext', self.reddit_object['thread_post'])) - async_tasks_primary = [ + async_tasks_primary.extend([ self.call_tts(str(idx), comment['comment_body']) for idx, comment in enumerate(self.reddit_object['comments']) - ] + ]) + async_tasks_primary_results = list() for task in track( as_completed(async_tasks_primary), description='Saving...', total=async_tasks_primary.__len__() ): - await task + async_tasks_primary_results.append(await task) + async_tasks_primary.clear() print_substep('Saved Text to MP3 files successfully.', style='bold green') return [ comments for comments, condition in - zip(self.reddit_object['comments'], async_tasks_primary[async_tasks_offset:]) + zip(range(self.reddit_object['comments'].__len__()), async_tasks_primary_results) if condition ] @@ -85,10 +87,11 @@ class TTSEngine: filepath=f'{self.path}/{filename}.mp3' ) - clip_length = audio_length(f'assets/audio/{filename}.mp3') + clip_length = audio_length(f'{self.path}/{filename}.mp3') if self.__total_length + clip_length <= self.max_length: - self.max_length += clip_length + self.__total_length += clip_length + print(clip_length, '/', self.__total_length) return True return False diff --git a/TTS/streamlabs_polly.py b/TTS/streamlabs_polly.py index 9ec01cb..befa2a3 100644 --- a/TTS/streamlabs_polly.py +++ b/TTS/streamlabs_polly.py @@ -56,16 +56,16 @@ class StreamlabsPolly(BaseApiTTS): else get_random_voice(voices) ) - response = await self.client.post( + async with self.client.post( self.url, data={ 'voice': voice, 'text': text, 'service': 'polly', } - ) - speak_url = await( - await response.json() - )['speak_url'] + ) as response: + speak_url = await( + await response.json() + )['speak_url'] return await self.client.get(speak_url) diff --git a/video_creation/final_video.py b/video_creation/final_video.py index 7068b61..9845f4c 100755 --- a/video_creation/final_video.py +++ b/video_creation/final_video.py @@ -17,6 +17,7 @@ from moviepy.editor import ( ) from moviepy.video.io.ffmpeg_tools import ffmpeg_merge_video_audio, ffmpeg_extract_subclip from rich.console import Console +from rich.progress import track from utils.cleanup import cleanup from utils.console import print_step, print_substep @@ -67,32 +68,82 @@ def make_final_video( VideoFileClip.reH = lambda clip: clip.resize(width=H) opacity = settings.config['settings']['opacity'] - final_length = 0 + def create_audio_clip( + clip_title: str | int, + clip_start: float, + ) -> 'AudioFileClip': + return ( + AudioFileClip(f'assets/temp/mp3/{clip_title}.mp3') + .set_start(clip_start) + ) + + video_duration = 0 # Gather all audio clips - audio_clips = [AudioFileClip(f'assets/temp/mp3/{i}.mp3') for i in indexes_of_clips] - audio_clips.insert(0, AudioFileClip('assets/temp/mp3/title.mp3')) + audio_clips = list() + + audio_title = create_audio_clip( + 'title', + 0, + ) + video_duration += audio_title.duration + audio_clips.append(audio_title) + indexes_for_videos = list() + + for idx in track( + indexes_of_clips, + description='Gathering audio clips...', + ): + temp_audio_clip = create_audio_clip( + idx, + video_duration, + ) + if video_duration + temp_audio_clip.duration <= max_length: + video_duration += temp_audio_clip.duration + audio_clips.append(temp_audio_clip) + indexes_for_videos.append(idx) + audio_composite = concatenate_audioclips(audio_clips) - console.log(f'[bold green] Video Will Be: {audio_composite.length} Seconds Long') - # add title to video - image_clips = [] + console.log(f'[bold green] Video Will Be: {audio_composite.end} Seconds Long') # Gather all images - new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity) - image_clips.insert( - 0, - ImageClip('assets/temp/png/title.png') - .set_duration(audio_clips[0].duration) - .resize(width=W - 100) - .set_opacity(new_opacity), + new_opacity = 1 if opacity is None or float(opacity) >= 1 else float(opacity) # TODO move to pydentic and percents + + def create_image_clip( + image_title: str | int, + audio_start: float, + audio_end: float, + audio_duration: float, + ) -> 'ImageClip': + return ( + ImageClip(f'assets/temp/png/{image_title}.png') + .set_start(audio_start) + .set_end(audio_end) + .set_duration(audio_duration, change_end=False) + .set_opacity(new_opacity) + .resize(width=W - 100) + ) + + # add title to video + image_clips = list() + + image_clips.append( + create_image_clip( + 'title', + audio_clips[0].start, + audio_clips[0].end, + audio_clips[0].duration + ) ) - for i in indexes_of_clips: + for photo_idx in indexes_for_videos: image_clips.append( - ImageClip(f'assets/temp/png/comment_{i}.png') - .set_duration(audio_clips[i + 1].duration) - .resize(width=W - 100) - .set_opacity(new_opacity) + create_image_clip( + f'comment_{photo_idx}', + audio_clips[photo_idx].start, + audio_clips[photo_idx].end, + audio_clips[photo_idx].duration + ) ) # if os.path.exists("assets/mp3/posttext.mp3"): @@ -110,9 +161,11 @@ def make_final_video( image_concat.audio = audio_composite download_background(background_config) - chop_background_video(background_config, final_length) + chop_background_video(background_config, video_duration) background_clip = ( - VideoFileClip("assets/temp/background.mp4") + VideoFileClip('assets/temp/background.mp4') + .set_start(0) + .set_end(video_duration) .without_audio() .resize(height=H) .crop(x1=1166.6, y1=0, x2=2246.6, y2=1920) @@ -148,7 +201,7 @@ def make_final_video( ffmpeg_extract_subclip( 'assets/temp/temp.mp4', 0, - final.duration, + video_duration, targetname=f'results/{subreddit}/{filename}', ) else: @@ -160,15 +213,15 @@ def make_final_video( ffmpeg_extract_subclip( # check if this gets run 'assets/temp/temp_audio.mp4', 0, - final.duration, - targetname=f"results/{subreddit}/{filename}", + video_duration, + targetname=f'results/{subreddit}/{filename}', ) else: print('debug duck') ffmpeg_extract_subclip( 'assets/temp/temp.mp4', 0, - final.duration, + video_duration, targetname=f'results/{subreddit}/{filename}', ) print_step('Removing temporary files 🗑') diff --git a/video_creation/voices.py b/video_creation/voices.py index b4eaf1f..e792ec3 100644 --- a/video_creation/voices.py +++ b/video_creation/voices.py @@ -37,13 +37,22 @@ async def save_text_to_mp3( if voice.casefold() in map(lambda _: _.casefold(), TTSProviders): break print('Unknown Choice') - engine_instance = TTSEngine(get_case_insensitive_key_value(TTSProviders, voice), reddit_obj) - return await engine_instance.run() + TTS_instance = get_case_insensitive_key_value(TTSProviders, voice) + if TTS_instance == StreamlabsPolly or TTS_instance == TikTok: + from aiohttp import ClientSession + + async with ClientSession() as client: + engine_instance = TTSEngine(TTS_instance(client), reddit_obj) + results = await engine_instance.run() + else: + engine_instance = TTSEngine(TTS_instance, reddit_obj) + results = await engine_instance.run() + return results def get_case_insensitive_key_value( input_dict, - key + key, ) -> object: return next( (value for dict_key, value in input_dict.items() if dict_key.lower() == key.lower()),