Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

double quotes to simple quotes normalization #71

Merged
merged 1 commit into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 66 additions & 66 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
def check_python_version():
current_version = sys.version_info[:2] # (major, minor)
if current_version < min_python_version or current_version > max_python_version:
error = f"""********** Error: Your OS Python version is not compatible! (current: {current_version[0]}.{current_version[1]})
error = f'''********** Error: Your OS Python version is not compatible! (current: {current_version[0]}.{current_version[1]})
Please create a virtual python environment verrsion {min_python_version[0]}.{min_python_version[1]} or {max_python_version[0]}.{max_python_version[1]}
with conda or python -v venv **********"""
with conda or python -v venv **********'''
print(error)
return False
else:
return True

def check_and_install_requirements(file_path):
if not os.path.exists(file_path):
print(f"Warning: File {file_path} not found. Skipping package check.")
print(f'Warning: File {file_path} not found. Skipping package check.')
try:
from importlib.metadata import version, PackageNotFoundError
with open(file_path, 'r') as f:
Expand All @@ -39,42 +39,42 @@ def check_and_install_requirements(file_path):
try:
installed_version = version(pkg_name)
except PackageNotFoundError:
print(f"{package} is missing.")
print(f'{package} is missing.')
missing_packages.append(package)
pass

if missing_packages:
print("\nInstalling missing packages...")
print('\nInstalling missing packages...')
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "pip"] + missing_packages)
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'] + missing_packages)
except subprocess.CalledProcessError as e:
print(f"Failed to install packages: {e}")
print(f'Failed to install packages: {e}')
return False

from lib.functions import check_missing_files, download_model
for mod in models.keys():
if mod == "xtts":
mod_exists, err, list = check_missing_files(models[mod]["local"], models[mod]["files"])
if mod == 'xtts':
mod_exists, err, list = check_missing_files(models[mod]['local'], models[mod]['files'])
if mod_exists:
print("All specified xtts base model files are present in the folder.")
print('All specified xtts base model files are present in the folder.')
else:
print("The following files are missing:", list)
print(f"Downloading {mod} files . . .")
download_model(models[mod]["local"], models[mod]["url"])
print('The following files are missing:', list)
print(f'Downloading {mod} files . . .')
download_model(models[mod]['local'], models[mod]['url'])
return True
except Exception as e:
raise(f"An error occurred: {e}")
raise(f'An error occurred: {e}')

def check_dictionary():
unidic_path = unidic.DICDIR
dicrc = os.path.join(unidic_path, "dicrc")
dicrc = os.path.join(unidic_path, 'dicrc')
if not os.path.exists(dicrc) or os.path.getsize(dicrc) == 0:
try:
print("UniDic dictionary not found or incomplete. Downloading now...")
subprocess.run(["python", "-m", "unidic", "download"], check=True)
print('UniDic dictionary not found or incomplete. Downloading now...')
subprocess.run(['python', '-m', 'unidic', 'download'], check=True)
except subprocess.CalledProcessError as e:
print(f"Failed to download UniDic dictionary. Error: {e}")
raise SystemExit("Unable to continue without UniDic. Exiting...")
print(f'Failed to download UniDic dictionary. Error: {e}')
raise SystemExit('Unable to continue without UniDic. Exiting...')
return True
def is_port_in_use(port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
Expand All @@ -84,12 +84,12 @@ def main():
global script_mode, share, ebooks_dir

# Convert the list of languages to a string to display in the help text
lang_list_str = ", ".join(list(language_mapping.keys()))
lang_list_str = ', '.join(list(language_mapping.keys()))

# Argument parser to handle optional parameters with descriptions
parser = argparse.ArgumentParser(
description="Convert eBooks to Audiobooks using a Text-to-Speech model. You can either launch the Gradio interface or run the script in headless mode for direct conversion.",
epilog="""\
description='Convert eBooks to Audiobooks using a Text-to-Speech model. You can either launch the Gradio interface or run the script in headless mode for direct conversion.',
epilog='''\
Example usage:
Windows:
headless:
Expand All @@ -101,66 +101,66 @@ def main():
./ebook2audiobook.sh --headless --ebook 'path_to_ebook' --voice 'path_to_voice' --language en --custom_model 'model.zip'
Graphic Interface:
./ebook2audiobook.sh
""",
''',
formatter_class=argparse.RawTextHelpFormatter
)
options = [
"--script_mode", "--share", "--headless", "--ebook", "--ebooks_dir",
"--voice", "--language", "--device", "--custom_model",
"--custom_model_url", "--temperature",
"--length_penalty", "--repetition_penalty", "--top_k", "--top_p", "--speed",
"--enable_text_splitting", "--version", "--help"
'--script_mode', '--share', '--headless', '--ebook', '--ebooks_dir',
'--voice', '--language', '--device', '--custom_model',
'--custom_model_url', '--temperature',
'--length_penalty', '--repetition_penalty', '--top_k', '--top_p', '--speed',
'--enable_text_splitting', '--version', '--help'
]
parser.add_argument(options[0], type=str,
help="Force the script to run in 'native' or 'docker_utils'")
parser.add_argument(options[1], action="store_true",
help="Enable a public shareable Gradio link. Defaults to False.")
help='Force the script to run in NATIVE or DOCKER_UTILS')
parser.add_argument(options[1], action='store_true',
help='Enable a public shareable Gradio link. Defaults to False.')
parser.add_argument(options[2], nargs='?', const=True, default=False,
help="Run in headless mode. Defaults to True if the flag is present without a value, False otherwise.")
help='Run in headless mode. Defaults to True if the flag is present without a value, False otherwise.')
parser.add_argument(options[3], type=str,
help="Path to the ebook file for conversion. Required in headless mode.")
parser.add_argument(options[4], nargs='?', const="default", type=str,
help=f"Path to the directory containing ebooks for batch conversion. Defaults to '{os.path.basename(ebooks_dir)}' if 'default' value is provided.")
help='Path to the ebook file for conversion. Required in headless mode.')
parser.add_argument(options[4], nargs='?', const='default', type=str,
help=f'Path to the directory containing ebooks for batch conversion. Defaults to "{os.path.basename(ebooks_dir)}" if "default" is provided.')
parser.add_argument(options[5], type=str,
help="Path to the target voice file for TTS. Optional, uses a default voice if not provided.")
help='Path to the target voice file for TTS. Optional, uses a default voice if not provided.')
parser.add_argument(options[6], type=str, default=default_language_code,
help=f"Language for the audiobook conversion. Options: {lang_list_str}. Default to English (eng).")
parser.add_argument(options[7], type=str, default="cpu", choices=["cpu", "gpu"],
help=f"Type of processor unit for the audiobook conversion. If not specified: check first if gpu available, if not cpu is selected.")
help=f'Language for the audiobook conversion. Options: {lang_list_str}. Default to English (eng).')
parser.add_argument(options[7], type=str, default='cpu', choices=['cpu', 'gpu'],
help=f'Type of processor unit for the audiobook conversion. If not specified: check first if gpu available, if not cpu is selected.')
parser.add_argument(options[8], type=str,
help="Path to the custom model file (.pth). Required if using a custom model.")
help='Path to the custom model file (.pth). Required if using a custom model.')
parser.add_argument(options[9], type=str,
help=("URL to download the custom model as a zip file. Optional, but will be used if provided. "
"Examples include David Attenborough's model: "
"'https://huggingface.co/drewThomasson/xtts_David_Attenborough_fine_tune/resolve/main/Finished_model_files.zip?download=true'. "
"More XTTS fine-tunes can be found on my Hugging Face at 'https://huggingface.co/drewThomasson'."))
parser.add_argument(options[10], type=float, default=0.65,
help="Temperature for the model. Defaults to 0.65. Higher temperatures lead to more creative outputs.")
help='Temperature for the model. Defaults to 0.65. Higher temperatures lead to more creative outputs.')
parser.add_argument(options[11], type=float, default=1.0,
help="A length penalty applied to the autoregressive decoder. Defaults to 1.0. Not applied to custom models.")
help='A length penalty applied to the autoregressive decoder. Defaults to 1.0. Not applied to custom models.')
parser.add_argument(options[12], type=float, default=2.0,
help="A penalty that prevents the autoregressive decoder from repeating itself. Defaults to 2.0.")
help='A penalty that prevents the autoregressive decoder from repeating itself. Defaults to 2.0')
parser.add_argument(options[13], type=int, default=50,
help="Top-k sampling. Lower values mean more likely outputs and increased audio generation speed. Defaults to 50.")
help='Top-k sampling. Lower values mean more likely outputs and increased audio generation speed. Defaults to 50')
parser.add_argument(options[14], type=float, default=0.8,
help="Top-p sampling. Lower values mean more likely outputs and increased audio generation speed. Defaults to 0.8.")
help='Top-p sampling. Lower values mean more likely outputs and increased audio generation speed. Defaults to 0.8')
parser.add_argument(options[15], type=float, default=1.0,
help="Speed factor for the speech generation. Defaults to 1.0.")
parser.add_argument(options[16], action="store_true",
help="Enable splitting text into sentences. Defaults to False.")
parser.add_argument(options[17], action="version",version=f"ebook2audiobook version {version}",
help="Show the version of the script and exit")
help='Speed factor for the speech generation. Defaults to 1.0')
parser.add_argument(options[16], action='store_true',
help='Enable splitting text into sentences. Defaults to False.')
parser.add_argument(options[17], action='version',version=f'ebook2audiobook version {version}',
help='Show the version of the script and exit')

for arg in sys.argv:
if arg.startswith("--") and arg not in options:
print(f"Error: Unrecognized option '{arg}'")
if arg.startswith('--') and arg not in options:
print(f'Error: Unrecognized option "{arg}"')
sys.exit(1)

args = parser.parse_args()

# Check if the port is already in use to prevent multiple launches
if not args.headless and is_port_in_use(gradio_interface_port):
print(f"Error: Port {gradio_interface_port} is already in use. The web interface may already be running.")
print(f'Error: Port {gradio_interface_port} is already in use. The web interface may already be running.')
sys.exit(1)

script_mode = args.script_mode if args.script_mode else script_mode
Expand All @@ -169,13 +169,13 @@ def main():
if script_mode == NATIVE:
check_pkg = check_and_install_requirements(requirements_file)
if check_pkg:
print("Package requirements ok")
print('Package requirements ok')
if check_dictionary():
print ("Dictionary ok")
print ('Dictionary ok')
else:
sys.exit(1)
else:
print("Some packages could not be installed")
print('Some packages could not be installed')
sys.exit(1)

from lib.functions import web_interface, convert_ebook
Expand All @@ -184,47 +184,47 @@ def main():
if args.headless:
# Condition to stop if both --ebook and --ebooks_dir are provided
if args.ebook and args.ebooks_dir:
print("Error: You cannot specify both --ebook and --ebooks_dir in headless mode.")
print('Error: You cannot specify both --ebook and --ebooks_dir in headless mode.')
sys.exit(1)

args.session = None

# Condition 1: If --ebooks_dir exists, check value and set 'ebooks_dir'
if args.ebooks_dir:
if args.ebooks_dir == "default":
print(f"Using the default ebooks_dir: {ebooks_dir}")
if args.ebooks_dir == 'default':
print(f'Using the default ebooks_dir: {ebooks_dir}')
ebooks_dir = os.path.abspath(ebooks_dir)
else:
# Check if the directory exists
if os.path.exists(args.ebooks_dir):
ebooks_dir = os.path.abspath(args.ebooks_dir)
else:
print(f"Error: The provided --ebooks_dir '{args.ebooks_dir}' does not exist.")
print(f'Error: The provided --ebooks_dir "{args.ebooks_dir}" does not exist.')
sys.exit(1)

if os.path.exists(ebooks_dir):
for file in os.listdir(ebooks_dir):
# Process files with supported ebook formats
if any(file.endswith(ext) for ext in ebook_formats):
full_path = os.path.join(ebooks_dir, file)
print(f"Processing eBook file: {full_path}")
print(f'Processing eBook file: {full_path}')
args.ebook = full_path
progress_status, audiobook_file = convert_ebook(args)
if audiobook_file is None:
print(f"Conversion failed: {progress_status}")
print(f'Conversion failed: {progress_status}')
sys.exit(1)
else:
print(f"Error: The directory {ebooks_dir} does not exist.")
print(f'Error: The directory {ebooks_dir} does not exist.')
sys.exit(1)

elif args.ebook:
progress_status, audiobook_file = convert_ebook(args)
if audiobook_file is None:
print(f"Conversion failed: {progress_status}")
print(f'Conversion failed: {progress_status}')
sys.exit(1)

else:
print("Error: In headless mode, you must specify either an ebook file using --ebook or an ebook directory using --ebooks_dir.")
print('Error: In headless mode, you must specify either an ebook file using --ebook or an ebook directory using --ebooks_dir.')
sys.exit(1)
else:
passed_arguments = sys.argv[1:]
Expand All @@ -233,7 +233,7 @@ def main():
if passed_args_set.issubset(allowed_arguments):
web_interface(script_mode, share)
else:
print("Error: In non-headless mode, no option or only '--share' can be passed")
print('Error: In non-headless mode, no option or only --share can be passed')
sys.exit(1)

if __name__ == '__main__':
Expand Down
50 changes: 25 additions & 25 deletions lib/conf.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
import os

NATIVE = "native"
DOCKER_UTILS = "docker_utils"
FULL_DOCKER = "full_docker"
NATIVE = 'native'
DOCKER_UTILS = 'docker_utils'
FULL_DOCKER = 'full_docker'

version = "2.0.0"
version = '2.0.0'
min_python_version = (3,10)
max_python_version = (3,12)

requirements_file = os.path.abspath(os.path.join(".","requirements.txt"))
requirements_file = os.path.abspath(os.path.join('.','requirements.txt'))

docker_utils_image = 'utils'
gradio_interface_port = 7860
gradio_shared_expire = 72 # hours
concurrency_limit = 16 # or None for unlimited

python_env_dir = os.path.abspath(os.path.join(".","python_env"))
models_dir = os.path.abspath(os.path.join(".","models"))
ebooks_dir = os.path.abspath(os.path.join(".","ebooks"))
processes_dir = os.path.abspath(os.path.join(".","tmp"))
audiobooks_gradio_dir = os.path.abspath(os.path.join(".","audiobooks","gui","gradio"))
audiobooks_host_dir = os.path.abspath(os.path.join(".","audiobooks","gui","host"))
audiobooks_cli_dir = os.path.abspath(os.path.join(".","audiobooks","cli"))
python_env_dir = os.path.abspath(os.path.join('.','python_env'))
models_dir = os.path.abspath(os.path.join('.','models'))
ebooks_dir = os.path.abspath(os.path.join('.','ebooks'))
processes_dir = os.path.abspath(os.path.join('.','tmp'))
audiobooks_gradio_dir = os.path.abspath(os.path.join('.','audiobooks','gui','gradio'))
audiobooks_host_dir = os.path.abspath(os.path.join('.','audiobooks','gui','host'))
audiobooks_cli_dir = os.path.abspath(os.path.join('.','audiobooks','cli'))

# <<<<<<< HEAD
os.environ["CALIBRE_TEMP_DIR"] = processes_dir
os.environ["CALIBRE_CACHE_DIRECTORY"] = processes_dir
os.environ["CALIBRE_NO_NATIVE_FILEDIALOGS"] = "1"
os.environ["DO_NOT_TRACK"] = "true"
os.environ["HUGGINGFACE_HUB_CACHE"] = models_dir
os.environ["HF_HOME"] = models_dir
os.environ["HF_DATASETS_CACHE"] = models_dir
os.environ["HF_TOKEN_PATH"] = os.path.join(os.path.expanduser("~"), ".huggingface_token")
os.environ["TTS_CACHE"] = models_dir
os.environ["TORCH_HOME"] = models_dir
os.environ["XDG_CACHE_HOME"] = models_dir
os.environ['CALIBRE_TEMP_DIR'] = processes_dir
os.environ['CALIBRE_CACHE_DIRECTORY'] = processes_dir
os.environ['CALIBRE_NO_NATIVE_FILEDIALOGS'] = '1'
os.environ['DO_NOT_TRACK'] = 'true'
os.environ['HUGGINGFACE_HUB_CACHE'] = models_dir
os.environ['HF_HOME'] = models_dir
os.environ['HF_DATASETS_CACHE'] = models_dir
os.environ['HF_TOKEN_PATH'] = os.path.join(os.path.expanduser('~'), '.huggingface_token')
os.environ['TTS_CACHE'] = models_dir
os.environ['TORCH_HOME'] = models_dir
os.environ['XDG_CACHE_HOME'] = models_dir

models = {
"xtts": {
Expand All @@ -52,8 +52,8 @@
}

ebook_formats = ['.epub', '.mobi', '.azw3', 'fb2', 'lrf', 'rb', 'snb', 'tcr', '.pdf', '.txt', '.rtf', '.docx', '.html', '.odt', '.azw']
audiobook_format = "m4b"
audio_proc_format = "wav" # only "wav" is valid for now
audiobook_format = 'm4b'
audio_proc_format = 'wav' # only 'wav' is valid for now

xtts_fine_tuned_voice_actors = {
"David Attenborough": {
Expand Down
Loading