File size: 12,586 Bytes
6f192dc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
import argparse
import os
import regex as re
import socket
import subprocess
import sys
import unidic
from lib.conf import *
from lib.lang import language_mapping, default_language_code
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]})
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 **********'''
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.')
try:
from importlib.metadata import version, PackageNotFoundError
with open(file_path, 'r') as f:
contents = f.read().replace('\r', '\n')
packages = [pkg.strip() for pkg in contents.splitlines() if pkg.strip()]
missing_packages = []
for package in packages:
# Extract package name without version specifier
pkg_name = re.split(r'[<>=]', package)[0].strip()
try:
installed_version = version(pkg_name)
except PackageNotFoundError:
print(f'{package} is missing.')
missing_packages.append(package)
pass
if missing_packages:
print('\nInstalling missing packages...')
try:
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'] + missing_packages)
except subprocess.CalledProcessError as 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_exists:
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'])
'''
return True
except Exception as e:
raise(f'An error occurred: {e}')
def check_dictionary():
unidic_path = unidic.DICDIR
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)
except subprocess.CalledProcessError as e:
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:
return s.connect_ex(('0.0.0.0', port)) == 0
def main():
global is_gui_process
# Convert the list of languages to a string to display in the help text
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='''
Example usage:
Windows:
headless:
ebook2audiobook.cmd --headless --ebook 'path_to_ebook' --voice 'path_to_voice'
Graphic Interface:
ebook2audiobook.cmd
Linux/Mac:
headless:
./ebook2audiobook.sh --headless --ebook 'path_to_ebook' --voice 'path_to_voice'
Graphic Interface:
./ebook2audiobook.sh
''',
formatter_class=argparse.RawTextHelpFormatter
)
options = [
'--script_mode', '--share', '--headless',
'--session', '--ebook', '--ebooks_dir',
'--voice', '--language', '--device',
#'--custom_model',
#'--custom_model_url',
'--temperature',
'--length_penalty', '--repetition_penalty',
'--top_k', '--top_p', '--speed',
'--enable_text_splitting', '--fine_tuned',
'--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. Default to False.')
parser.add_argument(options[2], nargs='?', const=True, default=False,
help='Run in headless mode. Default to True if the flag is present without a value, False otherwise.')
parser.add_argument(options[3], type=str,
help='Session to reconnect in case of interruption (headless mode only)')
parser.add_argument(options[4], type=str,
help='Path to the ebook file for conversion. Required in headless mode.')
parser.add_argument(options[5], nargs='?', const='default', type=str,
help=f'Path to the directory containing ebooks for batch conversion. Default to "{os.path.basename(ebooks_dir)}" if "default" is provided.')
parser.add_argument(options[6], type=str, default=None,
help='Path to the target voice file for TTS. Optional, uses a default voice if not provided.')
parser.add_argument(options[7], 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[8], 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[9], type=str,
help='Path to the custom model file (.pth). Required if using a custom model.')
parser.add_argument(options[10], 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[9], type=float, default=0.65,
help='Temperature for the model. Default to 0.65. Higher temperatures lead to more creative outputs.')
parser.add_argument(options[10], type=float, default=1.0,
help='A length penalty applied to the autoregressive decoder. Default to 1.0. Not applied to custom models.')
parser.add_argument(options[11], type=float, default=2.5,
help='A penalty that prevents the autoregressive decoder from repeating itself. Default to 2.5')
parser.add_argument(options[12], type=int, default=50,
help='Top-k sampling. Lower values mean more likely outputs and increased audio generation speed. Default to 50')
parser.add_argument(options[13], type=float, default=0.8,
help='Top-p sampling. Lower values mean more likely outputs and increased audio generation speed. Default to 0.8')
parser.add_argument(options[14], type=float, default=1.0,
help='Speed factor for the speech generation. Default to 1.0')
parser.add_argument(options[15], type=str, default=default_fine_tuned,
help='Name of the fine tuned model. Optional, uses the standard model according to the TTS engine and language.')
parser.add_argument(options[16], action='store_true',
help='Enable splitting text into sentences. Default 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}"')
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.')
sys.exit(1)
args.script_mode = args.script_mode if args.script_mode else NATIVE
args.share = args.share if args.share else False
if args.script_mode == NATIVE:
check_pkg = check_and_install_requirements(requirements_file)
if check_pkg:
print('Package requirements ok')
if check_dictionary():
print ('Dictionary ok')
else:
sys.exit(1)
else:
print('Some packages could not be installed')
sys.exit(1)
from lib.functions import web_interface, convert_ebook
# Conditions based on the --headless flag
if args.headless:
args.is_gui_process = False
args.audiobooks_dir = audiobooks_cli_dir
# 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.')
sys.exit(1)
# Condition 1: If --ebooks_dir exists, check value and set 'ebooks_dir'
if args.ebooks_dir:
new_ebooks_dir = None
if args.ebooks_dir == 'default':
print(f'Using the default ebooks_dir: {ebooks_dir}')
new_ebooks_dir = os.path.abspath(ebooks_dir)
else:
# Check if the directory exists
if os.path.exists(args.ebooks_dir):
new_ebooks_dir = os.path.abspath(args.ebooks_dir)
else:
print(f'Error: The provided --ebooks_dir "{args.ebooks_dir}" does not exist.')
sys.exit(1)
if os.path.exists(new_ebooks_dir):
for file in os.listdir(new_ebooks_dir):
# Process files with supported ebook formats
if any(file.endswith(ext) for ext in ebook_formats):
full_path = os.path.join(new_ebooks_dir, file)
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}')
sys.exit(1)
else:
print(f'Error: The directory {new_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}')
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.')
sys.exit(1)
else:
args.is_gui_process = True
passed_arguments = sys.argv[1:]
allowed_arguments = {'--share', '--script_mode'}
passed_args_set = {arg for arg in passed_arguments if arg.startswith('--')}
if passed_args_set.issubset(allowed_arguments):
web_interface(args)
else:
print('Error: In non-headless mode, no option or only --share can be passed')
sys.exit(1)
if __name__ == '__main__':
if not check_python_version():
sys.exit(1)
else:
main()
|