Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import argparse
|
2 |
+
import os
|
3 |
+
import regex as re
|
4 |
+
import socket
|
5 |
+
import subprocess
|
6 |
+
import sys
|
7 |
+
import unidic
|
8 |
+
|
9 |
+
from lib.conf import *
|
10 |
+
from lib.lang import language_mapping, default_language_code
|
11 |
+
|
12 |
+
# To download the voices folder
|
13 |
+
import download_github_folder
|
14 |
+
# Call the main function to execute the downloading process
|
15 |
+
download_github_folder.main()
|
16 |
+
|
17 |
+
def check_python_version():
|
18 |
+
current_version = sys.version_info[:2] # (major, minor)
|
19 |
+
if current_version < min_python_version or current_version > max_python_version:
|
20 |
+
error = f'''********** Error: Your OS Python version is not compatible! (current: {current_version[0]}.{current_version[1]})
|
21 |
+
Please create a virtual python environment verrsion {min_python_version[0]}.{min_python_version[1]} or {max_python_version[0]}.{max_python_version[1]}
|
22 |
+
with conda or python -v venv **********'''
|
23 |
+
print(error)
|
24 |
+
return False
|
25 |
+
else:
|
26 |
+
return True
|
27 |
+
|
28 |
+
def check_and_install_requirements(file_path):
|
29 |
+
if not os.path.exists(file_path):
|
30 |
+
print(f'Warning: File {file_path} not found. Skipping package check.')
|
31 |
+
try:
|
32 |
+
from importlib.metadata import version, PackageNotFoundError
|
33 |
+
with open(file_path, 'r') as f:
|
34 |
+
contents = f.read().replace('\r', '\n')
|
35 |
+
packages = [pkg.strip() for pkg in contents.splitlines() if pkg.strip()]
|
36 |
+
|
37 |
+
missing_packages = []
|
38 |
+
for package in packages:
|
39 |
+
# Extract package name without version specifier
|
40 |
+
pkg_name = re.split(r'[<>=]', package)[0].strip()
|
41 |
+
try:
|
42 |
+
installed_version = version(pkg_name)
|
43 |
+
except PackageNotFoundError:
|
44 |
+
print(f'{package} is missing.')
|
45 |
+
missing_packages.append(package)
|
46 |
+
pass
|
47 |
+
|
48 |
+
if missing_packages:
|
49 |
+
print('\nInstalling missing packages...')
|
50 |
+
try:
|
51 |
+
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'] + missing_packages)
|
52 |
+
except subprocess.CalledProcessError as e:
|
53 |
+
print(f'Failed to install packages: {e}')
|
54 |
+
return False
|
55 |
+
|
56 |
+
return True
|
57 |
+
except Exception as e:
|
58 |
+
raise(f'An error occurred: {e}')
|
59 |
+
|
60 |
+
def check_dictionary():
|
61 |
+
unidic_path = unidic.DICDIR
|
62 |
+
dicrc = os.path.join(unidic_path, 'dicrc')
|
63 |
+
if not os.path.exists(dicrc) or os.path.getsize(dicrc) == 0:
|
64 |
+
try:
|
65 |
+
print('UniDic dictionary not found or incomplete. Downloading now...')
|
66 |
+
subprocess.run(['python', '-m', 'unidic', 'download'], check=True)
|
67 |
+
except subprocess.CalledProcessError as e:
|
68 |
+
print(f'Failed to download UniDic dictionary. Error: {e}')
|
69 |
+
raise SystemExit('Unable to continue without UniDic. Exiting...')
|
70 |
+
return True
|
71 |
+
|
72 |
+
def is_port_in_use(port):
|
73 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
74 |
+
return s.connect_ex(('0.0.0.0', port)) == 0
|
75 |
+
|
76 |
+
def main():
|
77 |
+
global is_gui_process
|
78 |
+
|
79 |
+
# Convert the list of languages to a string to display in the help text
|
80 |
+
lang_list_str = ', '.join(list(language_mapping.keys()))
|
81 |
+
|
82 |
+
# Argument parser to handle optional parameters with descriptions
|
83 |
+
parser = argparse.ArgumentParser(
|
84 |
+
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.',
|
85 |
+
epilog='''
|
86 |
+
Example usage:
|
87 |
+
Windows:
|
88 |
+
headless:
|
89 |
+
ebook2audiobook.cmd --headless --ebook 'path_to_ebook'
|
90 |
+
Graphic Interface:
|
91 |
+
ebook2audiobook.cmd
|
92 |
+
Linux/Mac:
|
93 |
+
headless:
|
94 |
+
./ebook2audiobook.sh --headless --ebook 'path_to_ebook'
|
95 |
+
Graphic Interface:
|
96 |
+
./ebook2audiobook.sh
|
97 |
+
''',
|
98 |
+
formatter_class=argparse.RawTextHelpFormatter
|
99 |
+
)
|
100 |
+
options = [
|
101 |
+
'--script_mode', '--share', '--headless',
|
102 |
+
'--session', '--ebook', '--ebooks_dir',
|
103 |
+
'--voice', '--language', '--device', '--custom_model',
|
104 |
+
'--temperature', '--length_penalty', '--repetition_penalty',
|
105 |
+
'--top_k', '--top_p', '--speed',
|
106 |
+
'--enable_text_splitting', '--fine_tuned',
|
107 |
+
'--version', '--help'
|
108 |
+
]
|
109 |
+
parser.add_argument(options[0], type=str,
|
110 |
+
help='Force the script to run in NATIVE or DOCKER_UTILS')
|
111 |
+
parser.add_argument(options[1], action='store_true',
|
112 |
+
help='Enable a public shareable Gradio link. Default to False.')
|
113 |
+
parser.add_argument(options[2], nargs='?', const=True, default=False,
|
114 |
+
help='Run in headless mode. Default to True if the flag is present without a value, False otherwise.')
|
115 |
+
parser.add_argument(options[3], type=str,
|
116 |
+
help='Session to reconnect in case of interruption (headless mode only)')
|
117 |
+
parser.add_argument(options[4], type=str,
|
118 |
+
help='Path to the ebook file for conversion. Required in headless mode.')
|
119 |
+
parser.add_argument(options[5], nargs='?', const='default', type=str,
|
120 |
+
help=f'Path to the directory containing ebooks for batch conversion. Default to "{os.path.basename(ebooks_dir)}" if "default" is provided.')
|
121 |
+
parser.add_argument(options[6], type=str, default=None,
|
122 |
+
help='Path to the target voice file for TTS. Optional, must be 24khz for XTTS and 16khz for fairseq models, uses a default voice if not provided.')
|
123 |
+
parser.add_argument(options[7], type=str, default=default_language_code,
|
124 |
+
help=f'Language for the audiobook conversion. Options: {lang_list_str}. Default to English (eng).')
|
125 |
+
parser.add_argument(options[8], type=str, default='cpu', choices=['cpu', 'gpu'],
|
126 |
+
help=f'Type of processor unit for the audiobook conversion. If not specified: check first if gpu available, if not cpu is selected.')
|
127 |
+
parser.add_argument(options[9], type=str,
|
128 |
+
help=f'Path to the custom model (.zip file containing {default_model_files}). Required if using a custom model.')
|
129 |
+
parser.add_argument(options[10], type=float, default=0.65,
|
130 |
+
help='Temperature for the model. Default to 0.65. Higher temperatures lead to more creative outputs.')
|
131 |
+
parser.add_argument(options[11], type=float, default=1.0,
|
132 |
+
help='A length penalty applied to the autoregressive decoder. Default to 1.0. Not applied to custom models.')
|
133 |
+
parser.add_argument(options[12], type=float, default=2.5,
|
134 |
+
help='A penalty that prevents the autoregressive decoder from repeating itself. Default to 2.5')
|
135 |
+
parser.add_argument(options[13], type=int, default=50,
|
136 |
+
help='Top-k sampling. Lower values mean more likely outputs and increased audio generation speed. Default to 50')
|
137 |
+
parser.add_argument(options[14], type=float, default=0.8,
|
138 |
+
help='Top-p sampling. Lower values mean more likely outputs and increased audio generation speed. Default to 0.8')
|
139 |
+
parser.add_argument(options[15], type=float, default=1.0,
|
140 |
+
help='Speed factor for the speech generation. Default to 1.0')
|
141 |
+
parser.add_argument(options[16], action='store_true',
|
142 |
+
help='Enable splitting text into sentences. Default to False.')
|
143 |
+
parser.add_argument(options[17], type=str, default=default_fine_tuned,
|
144 |
+
help='Name of the fine tuned model. Optional, uses the standard model according to the TTS engine and language.')
|
145 |
+
parser.add_argument(options[18], action='version',version=f'ebook2audiobook version {version}',
|
146 |
+
help='Show the version of the script and exit')
|
147 |
+
|
148 |
+
for arg in sys.argv:
|
149 |
+
if arg.startswith('--') and arg not in options:
|
150 |
+
print(f'Error: Unrecognized option "{arg}"')
|
151 |
+
sys.exit(1)
|
152 |
+
|
153 |
+
args = vars(parser.parse_args())
|
154 |
+
|
155 |
+
# Check if the port is already in use to prevent multiple launches
|
156 |
+
if not args['headless'] and is_port_in_use(interface_port):
|
157 |
+
print(f'Error: Port {interface_port} is already in use. The web interface may already be running.')
|
158 |
+
sys.exit(1)
|
159 |
+
|
160 |
+
args['script_mode'] = args['script_mode'] if args['script_mode'] else NATIVE
|
161 |
+
args['share'] = args['share'] if args['share'] else False
|
162 |
+
|
163 |
+
if args['script_mode'] == NATIVE:
|
164 |
+
check_pkg = check_and_install_requirements(requirements_file)
|
165 |
+
if check_pkg:
|
166 |
+
if not check_dictionary():
|
167 |
+
sys.exit(1)
|
168 |
+
else:
|
169 |
+
print('Some packages could not be installed')
|
170 |
+
sys.exit(1)
|
171 |
+
|
172 |
+
from lib.functions import web_interface, convert_ebook
|
173 |
+
|
174 |
+
# Conditions based on the --headless flag
|
175 |
+
if args['headless']:
|
176 |
+
args['is_gui_process'] = False
|
177 |
+
args['audiobooks_dir'] = audiobooks_cli_dir
|
178 |
+
|
179 |
+
# Condition to stop if both --ebook and --ebooks_dir are provided
|
180 |
+
if args['ebook'] and args['ebooks_dir']:
|
181 |
+
print('Error: You cannot specify both --ebook and --ebooks_dir in headless mode.')
|
182 |
+
sys.exit(1)
|
183 |
+
|
184 |
+
# Condition 1: If --ebooks_dir exists, check value and set 'ebooks_dir'
|
185 |
+
if args['ebooks_dir']:
|
186 |
+
new_ebooks_dir = None
|
187 |
+
if args['ebooks_dir'] == 'default':
|
188 |
+
print(f'Using the default ebooks_dir: {ebooks_dir}')
|
189 |
+
new_ebooks_dir = os.path.abspath(ebooks_dir)
|
190 |
+
else:
|
191 |
+
# Check if the directory exists
|
192 |
+
if os.path.exists(args['ebooks_dir']):
|
193 |
+
new_ebooks_dir = os.path.abspath(args['ebooks_dir'])
|
194 |
+
else:
|
195 |
+
print(f'Error: The provided --ebooks_dir "{args["ebooks_dir"]}" does not exist.')
|
196 |
+
sys.exit(1)
|
197 |
+
|
198 |
+
if os.path.exists(new_ebooks_dir):
|
199 |
+
for file in os.listdir(new_ebooks_dir):
|
200 |
+
# Process files with supported ebook formats
|
201 |
+
if any(file.endswith(ext) for ext in ebook_formats):
|
202 |
+
full_path = os.path.join(new_ebooks_dir, file)
|
203 |
+
print(f'Processing eBook file: {full_path}')
|
204 |
+
args['ebook'] = full_path
|
205 |
+
progress_status, audiobook_file = convert_ebook(args)
|
206 |
+
if audiobook_file is None:
|
207 |
+
print(f'Conversion failed: {progress_status}')
|
208 |
+
sys.exit(1)
|
209 |
+
else:
|
210 |
+
print(f'Error: The directory {new_ebooks_dir} does not exist.')
|
211 |
+
sys.exit(1)
|
212 |
+
|
213 |
+
elif args['ebook']:
|
214 |
+
progress_status, audiobook_file = convert_ebook(args)
|
215 |
+
if audiobook_file is None:
|
216 |
+
print(f'Conversion failed: {progress_status}')
|
217 |
+
sys.exit(1)
|
218 |
+
|
219 |
+
else:
|
220 |
+
print('Error: In headless mode, you must specify either an ebook file using --ebook or an ebook directory using --ebooks_dir.')
|
221 |
+
sys.exit(1)
|
222 |
+
else:
|
223 |
+
args['is_gui_process'] = True
|
224 |
+
passed_arguments = sys.argv[1:]
|
225 |
+
allowed_arguments = {'--share', '--script_mode'}
|
226 |
+
passed_args_set = {arg for arg in passed_arguments if arg.startswith('--')}
|
227 |
+
if passed_args_set.issubset(allowed_arguments):
|
228 |
+
web_interface(args)
|
229 |
+
else:
|
230 |
+
print('Error: In non-headless mode, no option or only --share can be passed')
|
231 |
+
sys.exit(1)
|
232 |
+
|
233 |
+
if __name__ == '__main__':
|
234 |
+
if not check_python_version():
|
235 |
+
sys.exit(1)
|
236 |
+
else:
|
237 |
+
main()
|