Skip to content

Commit

Permalink
Utilities for installing & setup (#31)
Browse files Browse the repository at this point in the history
* Minor fixes

* Added prompts to install extras

* Added env variable creation for Azure

* Bumped version

* Minor
  • Loading branch information
osolmaz authored Dec 7, 2022
1 parent db562df commit 609b019
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 23 deletions.
56 changes: 56 additions & 0 deletions manim_voiceover/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re
import os
import sys
from typing import Union
import pip
import textwrap
from pydub import AudioSegment
Expand Down Expand Up @@ -209,3 +210,58 @@ def prompt_ask_missing_package(target_module: str, package_name: str):
pip.main(["install", package_name])
logger.info("Installed missing packages. Please run Manim again.")
sys.exit(0)


def prompt_ask_missing_extras(
target_module: Union[str, list],
extras: str,
dependent_item: str,
):
if isinstance(target_module, str):
target_modules = []
elif isinstance(target_module, list):
target_modules = target_module
else:
raise TypeError("target_module must be a string or a list of strings")

try:
for target_module in target_modules:
importlib.import_module(target_module)
return
except ImportError:
pass
logger.info(
f"The extra packages required by {dependent_item} are not installed. "
f"Shall I install them for you? [Y/n]"
)
answer = input()
if answer.lower() == "n":
raise ImportError(
f'{extras} extras are not installed. Install them by running `pip install "manim-voiceover[{extras}]"`'
)
else:
logger.info(f"Installing {extras}...")
pip.main(["install", f"manim-voiceover[{extras}]"])
logger.info("Installed missing extras. Please run Manim again.")
sys.exit(0)


def create_dotenv_file(required_variable_names: list, dotenv=".env"):
"""Create a .env file with the required variables"""
if os.path.exists(dotenv):
logger.info(
f"File {dotenv} already exists. Would you like to overwrite it? [Y/n]"
)
answer = input()
if answer.lower() == "n":
logger.info("Skipping .env file creation...")
return False

logger.info("Creating .env file...")
with open(dotenv, "w") as f:
for var_name in required_variable_names:
logger.info(f"Enter value for {var_name}:")
value = input()
f.write(f"{var_name}={value}\n")

return True
43 changes: 36 additions & 7 deletions manim_voiceover/services/azure.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import os
from pathlib import Path
import sys
from dotenv import load_dotenv, find_dotenv
from manim_voiceover.helper import remove_bookmarks
from manim_voiceover.helper import (
create_dotenv_file,
prompt_ask_missing_extras,
remove_bookmarks,
)
from manim import logger

try:
Expand All @@ -27,6 +32,18 @@ def serialize_word_boundary(wb):
}


def create_dotenv_azure():
logger.info(
"Check out https://voiceover.manim.community/en/stable/services.html#azureservice to learn how to create an account and get your subscription key."
)
if not create_dotenv_file(["AZURE_SUBSCRIPTION_KEY", "AZURE_SERVICE_REGION"]):
raise Exception(
"The environment variables AZURE_SUBSCRIPTION_KEY and AZURE_SERVICE_REGION are not set. Please set them or create a .env file with the variables."
)
logger.info("The .env file has been created. Please run Manim again.")
sys.exit()


class AzureService(SpeechService):
"""Speech service for Azure TTS API."""

Expand All @@ -46,6 +63,10 @@ def __init__(
output_format (str, optional): The output format to use. See the `API page <https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/rest-text-to-speech?tabs=streaming#audio-outputs>`__ for all the available options. Defaults to ``Audio48Khz192KBitRateMonoMp3``.
prosody (dict, optional): Global prosody settings to use. See the `API page <https://learn.microsoft.com/en-us/azure/cognitive-services/speech-service/speech-synthesis-markup#adjust-prosody>`__ for all the available options. Defaults to None.
"""
prompt_ask_missing_extras(
"azure.cognitiveservices.speech", "azure", "AzureService"
)

self.voice = voice
self.style = style
self.output_format = output_format
Expand All @@ -63,12 +84,9 @@ def generate_from_text(
cache_dir = self.cache_dir

# Apply prosody
prosody = self.prosody
if "prosody" in kwargs:
prosody = kwargs["prosody"]
prosody = kwargs.get("prosody", self.prosody)

if prosody is not None:
prosody = kwargs["prosody"]
if not isinstance(prosody, dict):
raise ValueError(
"The prosody argument must be a dict that contains at least one of the following keys: 'pitch', 'contour', 'range', 'rate', 'volume'."
Expand Down Expand Up @@ -127,9 +145,10 @@ def generate_from_text(
azure_subscription_key = os.environ["AZURE_SUBSCRIPTION_KEY"]
azure_service_region = os.environ["AZURE_SERVICE_REGION"]
except KeyError:
raise Exception(
"Microsoft Azure's text-to-speech API needs account credentials to connect. You can create an account for free and (as of writing this) get a free quota of TTS minutes. Check out the documentation for instructions."
logger.error(
"Could not find the environment variables AZURE_SUBSCRIPTION_KEY and AZURE_SERVICE_REGION. Microsoft Azure's text-to-speech API needs account credentials to connect. You can create an account for free and (as of writing this) get a free quota of TTS minutes."
)
create_dotenv_azure()

speech_config = speechsdk.SpeechConfig(
subscription=azure_subscription_key,
Expand Down Expand Up @@ -185,6 +204,16 @@ def process_event(evt):
logger.error(
"Error details: {}".format(cancellation_details.error_details)
)
if "authentication" in cancellation_details.error_details.lower():
logger.error(
"The authentication credentials are invalid. Please check the environment variables AZURE_SUBSCRIPTION_KEY and AZURE_SERVICE_REGION."
)
logger.info(
"Would you like to enter new values for the variables in the .env file? [Y/n]"
)
if input().lower() in ["y", "yes", ""]:
create_dotenv_azure()

raise Exception("Speech synthesis failed")

return json_dict
4 changes: 1 addition & 3 deletions manim_voiceover/services/coqui/synthesize.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
from TTS.utils.manage import ModelManager
from .utils_synthesizer import Synthesizer
except ImportError:
logger.error(
'Missing packages. Run `pip install TTS` to use CoquiService.'
)
logger.error("Missing packages. Run `pip install TTS` to use CoquiService.")

DEFAULT_MODEL = "tts_models/en/ljspeech/tacotron2-DDC"

Expand Down
5 changes: 3 additions & 2 deletions manim_voiceover/services/gtts.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from pathlib import Path
from manim import logger
from manim_voiceover.helper import remove_bookmarks
from manim_voiceover.helper import prompt_ask_missing_extras, remove_bookmarks

try:
from gtts import gTTS, gTTSError
except ImportError:
logger.error(
'Missing packages. Run `pip install "manim-voiceover[gtts]"` to use GoogleService.'
'Missing packages. Run `pip install "manim-voiceover[gtts]"` to use GTTSService.'
)

from manim_voiceover.services.base import SpeechService
Expand All @@ -26,6 +26,7 @@ def __init__(self, lang="en", tld="com", **kwargs):
for all the available options. Defaults to "en".
tld (str, optional): Top level domain of the Google Translate URL. Defaults to "com".
"""
prompt_ask_missing_extras("gtts", "gtts", "GTTSService")
SpeechService.__init__(self, **kwargs)
self.lang = lang
self.tld = tld
Expand Down
5 changes: 4 additions & 1 deletion manim_voiceover/services/pyttsx3.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from pathlib import Path
from manim import logger
from manim_voiceover.helper import prompt_ask_missing_extras

try:
from pyttsx3 import Engine
except ImportError:
logger.error(
'Missing packages. Run `pip install "manim-voiceover[pyttsx3]"` to use Pyttsx3Service.'
'Missing packages. Run `pip install "manim-voiceover[pyttsx3]"` to use PyTTSX3Service.'
)

from manim_voiceover.services.base import SpeechService
Expand All @@ -16,6 +17,8 @@ class PyTTSX3Service(SpeechService):

def __init__(self, engine=None, **kwargs):
""""""
prompt_ask_missing_extras("pyttsx3", "pyttsx3", "PyTTSX3Service")

if engine is None:
engine = Engine()

Expand Down
6 changes: 5 additions & 1 deletion manim_voiceover/services/recorder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from manim_voiceover.helper import msg_box, remove_bookmarks
from manim_voiceover.helper import msg_box, prompt_ask_missing_extras, remove_bookmarks

from manim_voiceover.services.base import SpeechService
from manim import logger
Expand Down Expand Up @@ -47,6 +47,10 @@ def __init__(
trim_buffer_start (int, optional): Buffer duration for trimming silence at the start. Defaults to 200 ms.
trim_buffer_end (int, optional): Buffer duration for trimming silence at the end. Defaults to 200 ms.
"""
prompt_ask_missing_extras(
["pyaudio", "pynput", "playsound"], "recorder", "RecorderService"
)

self.recorder = Recorder(
format=format,
channels=channels,
Expand Down
11 changes: 3 additions & 8 deletions manim_voiceover/services/recorder/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@

from manim_voiceover.helper import trim_silence, wav2mp3

try:
from pynput import keyboard
import pyaudio
import playsound
except ImportError:
logger.error(
'Missing packages. Run `pip install "manim-voiceover[recorder]"` to use RecorderService.'
)
from pynput import keyboard
import pyaudio
import playsound


class MyListener(keyboard.Listener):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "manim-voiceover"
version = "0.2.1.post0"
version = "0.2.1.post1"
description = "Manim plugin for all things voiceover"
authors = ["The Manim Community Developers <[email protected]>"]
license = "MIT"
Expand Down

0 comments on commit 609b019

Please sign in to comment.