From 25f578f48c05448d2082f10fd4965b4220f5d885 Mon Sep 17 00:00:00 2001 From: Paillat Date: Thu, 25 May 2023 21:47:11 +0200 Subject: [PATCH] fix(generators/ideas.py): fix typo in variable name 'existing_ideas' feat(generators/montage.py): add check to skip slide if it already exists feat(generators/montage.py): add support for DEEPL_ACCESS_KEY and UNSPLASH_ACCESS_KEY environment variables feat(generators/speak.py): add support for Johanne voice feat(generators/speak.py): add emotion parameter to generate_voice function feat(generators/uploader.py): add success message and authorization prompt message to run_local_server method fix(main.py): check if credits is None before writing to meta.txt file feat(prompts/marp.md): change theme to gaia and add lead and invert classes --- generators/ideas.py | 5 +- generators/montage.py | 105 ++++++++++++++++++++++++----------------- generators/speak.py | 20 +++++--- generators/uploader.py | 5 +- main.py | 7 +-- prompts/marp.md | 6 ++- 6 files changed, 90 insertions(+), 58 deletions(-) diff --git a/generators/ideas.py b/generators/ideas.py index 2d3543a..1fa1e46 100644 --- a/generators/ideas.py +++ b/generators/ideas.py @@ -20,7 +20,10 @@ async def generate_ideas(path, subject): except: ides_json = [] ideas = "There are no existing ideas." - prmpt = prmpt.replace('[existing ideas]', ideas) + exuisting_ideas = "" + for idea in ides_json: + exuisting_ideas += f"{idea['title']}\n" + prmpt = prmpt.replace('[existing ideas]', exuisting_ideas) print(prmpt) response = await openai.ChatCompletion.acreate( model="gpt-3.5-turbo", diff --git a/generators/montage.py b/generators/montage.py index 9909ef0..d0100ad 100644 --- a/generators/montage.py +++ b/generators/montage.py @@ -9,12 +9,12 @@ from generators.speak import generate_voice, voices from moviepy.video.VideoClip import ImageClip from moviepy.editor import VideoFileClip, concatenate_videoclips, CompositeAudioClip, concatenate_audioclips from moviepy.audio.io.AudioFileClip import AudioFileClip -from moviepy.audio.fx.all import volumex, audio_fadein, audio_fadeout +from moviepy.audio.fx.all import volumex, audio_fadein, audio_fadeout # type: ignore from dotenv import load_dotenv load_dotenv() -unsplash_access = os.getenv("UNSPLASH_ACCESS_KEY") +unsplash_access = os.getenv("UNSPLASH_ACCESS_KEY") or "UNSPLASH_ACCESS_KEY" unsplash_url = "https://api.unsplash.com/photos/random/?client_id=" + unsplash_access + "&query=" -deepl_access = os.getenv("DEEPL_ACCESS_KEY") +deepl_access = os.getenv("DEEPL_ACCESS_KEY") or "DEEPL_ACCESS_KEY" translator = deepl.Translator(deepl_access) def prepare(path): @@ -36,6 +36,9 @@ def prepare(path): if not os.path.exists(audio_path): generate_voice(audio_path, script[i]['spoken'], choosen_voice) if "image" in script[i]: + if os.path.exists(path + "/slides/assets/slide" + str(i) + ".md"): + #skip this slide + continue if not os.path.exists(path + "/slides/assets"): os.mkdir(path + "/slides/assets") url= unsplash_url + script[i]['image'] @@ -48,16 +51,29 @@ def prepare(path): with open(path + "/slides/slide" + str(i) + ".md", 'w', encoding='utf-8') as f: f.write(content) elif "markdown" in script[i]: + if os.path.exists(path + "/slides/slide" + str(i) + ".md"): + #skip this slide + continue with open(path + "/slides/slide" + str(i) + ".md", 'w', encoding='utf-8') as f: f.write(marp + "\n\n" + script[i]['markdown']) elif "huge" in script[i]: #use fit + if os.path.exists(path + "/slides/slide" + str(i) + ".md"): + #skip this slide + continue with open(path + "/slides/slide" + str(i) + ".md", 'w', encoding='utf-8') as f: f.write(marp + "\n\n# " + script[i]['huge']) else: - pass + if os.path.exists(path + "/slides/slide" + str(i) + ".md"): + #skip this slide + continue + with open(path + "/slides/slide" + str(i) + ".md", 'w', encoding='utf-8') as f: + f.write(marp + "\n\n") # blank slide for i in range(len(script)): marrkdown_path = "./" + path + "/slides/slide" + str(i) + ".md" + if os.path.exists(f"./{path}/slides/slide{i}.png"): + #skip this slide + continue command = f"marp.exe {marrkdown_path} -o {path}/slides/slide{i}.png --allow-local-files" os.system(command) return script @@ -79,42 +95,45 @@ def subs(length, total, text, srt, index): return srt def mount(path, script): - num_slides = len(os.listdir(path + "/audio")) - clips = [] - srt = pysrt.SubRipFile() - total_length = 0 - for i in range(num_slides): - audio = AudioFileClip(path + "/audio/audio" + str(i) + ".mp3") - complete_audio = CompositeAudioClip([ - AudioFileClip("silence.mp3").set_duration(1), - audio, - AudioFileClip("silence.mp3").set_duration(1) - ]) - length = complete_audio.duration - total_length += length - srt = subs(length, total_length, script[i]['spoken'], srt, i) - slide = ImageClip(path + "/slides/slide" + str(i) + ".png").set_duration(length) - slide = slide.set_audio(complete_audio) - clips.append(slide) - randmusic = random.choice(os.listdir("musics")) - while randmusic.endswith(".txt"): randmusic = random.choice(os.listdir("musics")) - randpath = "musics/" + randmusic - music = AudioFileClip(randpath).set_duration(total_length) - music = audio_fadein(music, 20) - music = audio_fadeout(music, 20) - music = volumex(music, 0.2) - musics = [] - if music.duration < total_length: - for i in range(int(total_length / music.duration)): - musics.append(music) - music = concatenate_audioclips(musics) - final_clip = concatenate_videoclips(clips, method="compose") - existing_audio = final_clip.audio - final_audio = CompositeAudioClip([existing_audio, music]) - final_clip = final_clip.set_audio(final_audio) - final_clip.write_videofile(path + "/montage.mp4", fps=60, codec="nvenc") - srt.save(path + "/montage.srt") - with open (randpath.split(".")[0] + ".txt", 'r', encoding='utf-8') as f: - music_credit = f.read() - f.close() - return music_credit \ No newline at end of file + if not os.path.exists(path + "/montage.mp4"): + num_slides = len(os.listdir(path + "/audio")) + clips = [] + srt = pysrt.SubRipFile() + total_length = 0 + for i in range(num_slides): + audio = AudioFileClip(path + "/audio/audio" + str(i) + ".mp3") + complete_audio = CompositeAudioClip([ + AudioFileClip("silence.mp3").set_duration(1), + audio, + AudioFileClip("silence.mp3").set_duration(1) + ]) + length = complete_audio.duration + total_length += length + srt = subs(length, total_length, script[i]['spoken'], srt, i) + slide = ImageClip(path + "/slides/slide" + str(i) + ".png").set_duration(length) + slide = slide.set_audio(complete_audio) + clips.append(slide) + randmusic = random.choice(os.listdir("musics")) + while randmusic.endswith(".txt"): randmusic = random.choice(os.listdir("musics")) + randpath = "musics/" + randmusic + music = AudioFileClip(randpath).set_duration(total_length) + music = audio_fadein(music, 20) + music = audio_fadeout(music, 20) + music = volumex(music, 0.2) + musics = [] + if music.duration < total_length: + for i in range(int(total_length / music.duration)): + musics.append(music) + music = concatenate_audioclips(musics) + final_clip = concatenate_videoclips(clips, method="compose") + existing_audio = final_clip.audio + final_audio = CompositeAudioClip([existing_audio, music]) + final_clip = final_clip.set_audio(final_audio) + final_clip.write_videofile(path + "/montage.mp4", fps=60, codec="nvenc") + srt.save(path + "/montage.srt") + with open (randpath.split(".")[0] + ".txt", 'r', encoding='utf-8') as f: + music_credit = f.read() + f.close() + return music_credit + else: + return None \ No newline at end of file diff --git a/generators/speak.py b/generators/speak.py index 95b49ab..fc70347 100644 --- a/generators/speak.py +++ b/generators/speak.py @@ -8,17 +8,23 @@ fakenames = { "Alexander": "p230", "Benjamin": "p240", "Amelia": "p270", - "Katherine": "p273" + "Katherine": "p273", + "Johanne": "p347", } -voices = ["Alexander", "Benjamin", "Amelia", "Katherine"] +voices = ["Alexander", "Benjamin", "Amelia", "Katherine", "Johanne"] # Init TTS def generate_voice(path, text, speaker="Alexander"): - try: - tts = TTS(model_best_multi, gpu=True) - except: - tts = TTS(model_best_multi, gpu=False) + model = model_best_multi speaker = fakenames[speaker] if speaker in fakenames else speaker - tts.tts_to_file(text=text, file_path=path, speaker=speaker, speed=1) \ No newline at end of file + print(f"Generating voice for {model} with speaker {speaker}") + try: + tts = TTS(model, gpu=True) + except: + tts = TTS(model, gpu=False) + tts.tts_to_file(text=text, file_path=path, speaker=speaker, speed=1, emotion="Happy") + +if __name__ == "__main__": + generate_voice("test/test.mp3", "This is a test. I like the words python, django and flask. Betty bought a bit of butter but the butter was bitter. So she bought some better butter to make the bitter butter better.") \ No newline at end of file diff --git a/generators/uploader.py b/generators/uploader.py index c4d10a6..e8e494c 100644 --- a/generators/uploader.py +++ b/generators/uploader.py @@ -26,7 +26,7 @@ RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError, client.NotConnected, RETRIABLE_STATUS_CODES = [500, 502, 503, 504] -CLIENT_SECRETS_FILE = 'env/client_secret.json' +CLIENT_SECRETS_FILE = '' #SCOPES = ['https://www.googleapis.com/auth/youtube.upload', 'https://www.googleapis.com/upload/youtube/v3/thumbnails/set', 'https://www.googleapis.com/auth/youtube.force-ssl'] SCOPES = ['https://www.googleapis.com/auth/youtube'] @@ -38,6 +38,7 @@ VALID_PRIVACY_STATUSES = ('public', 'private', 'unlisted') # Authorize the request and store authorization credentials. def get_authenticated_service(credentialsPath=""): + CLIENT_SECRETS_FILE=f'{credentialsPath}/client_secret.json' if os.path.exists(f'{credentialsPath}/credentials.json'): with open(f'{credentialsPath}/credentials.json') as json_file: data = json.load(json_file) @@ -52,7 +53,7 @@ def get_authenticated_service(credentialsPath=""): else: flow = InstalledAppFlow.from_client_secrets_file( CLIENT_SECRETS_FILE, SCOPES) - credentials = flow.run_local_server() + credentials = flow.run_local_server(success_message="Heyy, yippie, you're authenticated ! You can close this window now !", authorization_prompt_message="Please authorize this app to upload videos on your YouTube account !") with open(f'{credentialsPath}/credentials.json', 'w') as outfile: outfile.write(credentials.to_json()) return build(API_SERVICE_NAME, API_VERSION, credentials=credentials) diff --git a/main.py b/main.py index 38d4aa1..39b2e86 100644 --- a/main.py +++ b/main.py @@ -53,9 +53,10 @@ async def main(): script = prepare(path) credits = mount(path, script) description = f"{idea['description']}\n\nMusic credits: {credits}" - with open(path + "/meta.txt", 'w', encoding='utf-8') as f: - f.write(description) - f.close() + if credits != None: + with open(path + "/meta.txt", 'w', encoding='utf-8') as f: + f.write(description) + f.close() generate_miniature(path, title=idea['title'], description=idea['description']) upload_video(path, idea['title'], description, 28, "", "private", subjectdirpath) print(f"Your video is ready! You can find it in {path}.") diff --git a/prompts/marp.md b/prompts/marp.md index 193e055..7df607d 100644 --- a/prompts/marp.md +++ b/prompts/marp.md @@ -1,6 +1,8 @@ --- marp: true -theme: default -class: invert +theme: gaia +class: + - lead + - invert backgroundImage: url(https://images.unsplash.com/photo-1651604454911-fdfb0edde727) --- \ No newline at end of file