Files
ebook2audiobook/lib/gradio.py
unknown 030f87e600 ...
2025-12-29 04:53:30 -08:00

2666 lines
157 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from lib.core import *
from lib.classes.tts_engines.common.preset_loader import load_engine_presets
def build_interface(args:dict)->gr.Blocks:
try:
script_mode = args['script_mode']
is_gui_process = args['is_gui_process']
is_gui_shared = args['share']
title = 'Ebook2Audiobook'
gr_glassmask_msg = 'Initialization, please wait...'
models = None
ebook_src = None
language_options = [
(
f"{details['name']} - {details['native_name']}" if details['name'] != details['native_name'] else details['name'],
lang
)
for lang, details in language_mapping.items()
]
voice_options = []
tts_engine_options = []
custom_model_options = []
fine_tuned_options = []
audiobook_options = []
options_output_split_hours = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
src_label_file = 'Upload File'
src_label_dir = 'Select a Directory'
visible_gr_tab_xtts_params = interface_component_options['gr_tab_xtts_params']
visible_gr_tab_bark_params = interface_component_options['gr_tab_bark_params']
visible_gr_group_custom_model = interface_component_options['gr_group_custom_model']
visible_gr_group_voice_file = interface_component_options['gr_group_voice_file']
theme = gr.themes.Origin(
primary_hue='green',
secondary_hue='amber',
neutral_hue='gray',
radius_size='lg',
font_mono=['JetBrains Mono', 'monospace', 'Consolas', 'Menlo', 'Liberation Mono']
)
header_css = '''
<style>
/* Global Scrollbar Customization */
/* The entire scrollbar */
::-webkit-scrollbar {
width: 6px !important;
height: 6px !important;
cursor: pointer !important;;
}
/* The scrollbar track (background) */
::-webkit-scrollbar-track {
background: none transparent !important;
border-radius: 6px !important;
}
/* The scrollbar thumb (scroll handle) */
::-webkit-scrollbar-thumb {
background: #c09340 !important;
border-radius: 6px !important;
}
/* The scrollbar thumb on hover */
::-webkit-scrollbar-thumb:hover {
background: #ff8c00 !important;
}
/* Firefox scrollbar styling */
html {
scrollbar-width: thin !important;
scrollbar-color: #c09340 none !important;
}
button div.wrap span {
display: none !important;
}
button div.wrap::after {
content: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231E90FF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4'/><polyline points='17 8 12 3 7 8'/><line x1='12' y1='3' x2='12' y2='15'/></svg>") !important;
width: 24px !important;
height: 24px !important;
display: inline-block !important;
vertical-align: middle !important;
}
body:has(#gr_convert_btn:disabled) table.file-preview button.label-clear-button {
display: none !important;
}
span[data-testid="block-info"] {
font-size: 12px !important;
}
/////////////////////
.wrap-inner {
border: 1px solid #666666;
}
.selected {
color: var(--secondary-500) !important;
text-shadow: 0.3px 0.3px 0.3px #303030;
}
.overflow-menu {
display: none !important;
}
.gr-glass-mask {
z-index: 9999 !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
background: rgba(0,0,0,0.5) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-size: 1.2rem !important;
color: #ffffff !important;
text-align: center !important;
border: none !important;
opacity: 1;
pointer-events: all !important;
}
.gr-glass-mask.hide {
animation: fadeOut 2s ease-out 2s forwards !important;
}
.small-btn{
background: var(--block-background-fill) !important;
font-size: 22px !important;
width: 60px !important;
height: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
.small-btn:hover {
background: var(--button-primary-background-fill-hover) !important;
font-size: 28px !important;
}
.small-btn-red{
background: var(--block-background-fill) !important;
font-size: 22px !important;
width: 60px !important;
height: 60px !important;
margin: 0 !important;
padding: 0 !important;
}
.small-btn-red:hover {
background-color: #ff5050 !important;
font-size: 28px !important;
}
.small-btn:active, .small-btn-red:active {
background: var(--body-text-color) !important;
font-size: 30px !important;
color: var(--body-background-fill) !important;
}
.file-preview-holder {
height: 116px !important;
overflow: auto !important;
}
.progress-bar.svelte-ls20lj {
background: var(--secondary-500) !important;
}
.file-preview-holder {
height: auto !important;
min-height: 0 !important;
max-height: none !important;
}
///////////////////
.gr-tab {
padding: 0 3px 0 3px !important;
margin: 0 !important;
border: none !important;
}
.gr-col {
padding: 0 6px 0 6px !important;
margin: 0 !important;
border: none !important;
}
.gr-group-main > div {
background: none !important;
border-radius: var(--radius-md) !important;
}
.gr-group > div {
background: none !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 0 var(--radius-md) var(--radius-md) var(--radius-md) !important;
}
.gr-group-sides-padded{
background: none !important;
margin: 0 var(--size-2) 0 var(--size-2)!important;;
border-radius: 0 var(--radius-md) var(--radius-md) var(--radius-md) !important;
}
.gr-group-convert-btn{
margin: var(--size-2) !important;;
border-radius: var(--radius-md) !important;
}
.gr-label textarea[data-testid="textbox"]{
padding: 0 0 0 3px !important;
margin: 0 !important;
text-align: left !important;
font-weight: normal !important;
height: auto !important;
font-size: 12px !important;
border: none !important;
overflow-y: hidden !important;
line-height: 12px !important;
}
.gr-markdown p {
margin-top: 8px !important;
width: 90px !important;
padding: 0 !important;
border-radius: var(--radius-md) var(--radius-md) 0 0 !important;
background: var(--block-background-fill) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
text-align: center !important;
}
.gr-markdown-span {
margin-top: 8px !important;
width: 90px !important;
padding: 0 !important;
border-radius: var(--radius-md) var(--radius-md) 0 0 !important;
background: var(--block-background-fill) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
text-align: center !important;
}
.gr-markdown-output-split-hours {
overflow: hidden !important;
background: var(--block-background-fill) !important;
border-radius: 0 !important;
font-size: 12px !important;
text-align: center !important;
vertical-align: middle !important;
padding-top: 4px !important;
padding-bottom: 4px !important;
white-space: nowrap !important;
}
.gr-voice-player {
margin: 0 !important;
padding: 0 !important;
width: 60px !important;
height: 60px !important;
background: var(--block-background-fill) !important;
}
.play-pause-button:hover svg {
fill: #ffab00 !important;
stroke: #ffab00 !important;
transform: scale(1.2) !important;
}
.gr-convert-btn {
font-size: 30px !important;
}
////////////////////
#gr_ebook_file, #gr_custom_model_file, #gr_voice_file {
height: 100px !important;
min-height: 100px !important;
max-height: 100px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
#gr_ebook_file label, #gr_custom_model_file label, #gr_voice_file label {
background: none !important;
border: none !important;
}
#gr_audiobook_player label {
display: none !important;
}
#gr_ebook_file button>div, #gr_custom_model_file button>div, #gr_voice_file button>div {
font-size: 12px !important;
}
#gr_ebook_file .empty, #gr_custom_model_file .empty, #gr_voice_file .empty,
#gr_ebook_file .wrap, #gr_custom_model_file .wrap, #gr_voice_file .wrap {
height: 100% !important;
min-height: 100px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
#gr_custom_model_file [aria-label="Clear"], #gr_voice_file [aria-label="Clear"] {
display: none !important;
}
#gr_fine_tuned_list {
height: 95px !important;
}
#gr_voice_list {
height: 60px !important;
}
#gr_output_format_list {
height: 103px !important;
}
#gr_row_output_split_hours {
border-radius: 0 !important;
}
#gr_progress .progress-bar {
background: #ff7b00 !important;
}
#gr_audiobook_sentence textarea{
margin: auto !important;
text-align: center !important;
}
#gr_session textarea, #gr_progress textarea {
overflow: hidden !important;
overflow-y: auto !important;
scrollbar-width: none !important;
}
#gr_session textarea::-webkit-scrollbar, #gr_progress textarea::-webkit-scrollbar {
display: none !important;
}
#gr_ebook_mode span[data-testid="block-info"],
#gr_language span[data-testid="block-info"],
#gr_voice_list span[data-testid="block-info"],
#gr_device span[data-testid="block-info"],
#gr_tts_engine_list span[data-testid="block-info"],
#gr_output_split_hours span[data-testid="block-info"],
#gr_session span[data-testid="block-info"],
#gr_custom_model_list span[data-testid="block-info"],
#gr_audiobook_sentence span[data-testid="block-info"],
#gr_audiobook_list span[data-testid="block-info"],
#gr_progress span[data-testid="block-info"]{
display: none !important;
}
#gr_row_ebook_mode { align-items: center !important; }
#gr_chapters_preview {
align-self: center !important;
overflow: visible !important;
padding: 20px 0 20px 10px !important;
}
#gr_group_output_split {
border-radius: 0 !important;
}
#gr_tts_rating {
overflow: hidden !important;
}
#gr_row_voice_player, #gr_row_custom_model_list, #gr_row_audiobook_list {
height: 60px !important;
}
#gr_audiobook_player :is(.volume, .empty, .source-selection, .control-wrapper, .settings-wrapper, label) {
display: none !important;
}
#gr_audiobook_files label[data-testid="block-label"] {
display: none !important;
}
#gr_audiobook_player audio {
width: 100% !important;
padding-top: 10px !important;
padding-bottom: 10px !important;
border-radius: 0px !important;
background-color: #ebedf0 !important;
color: #ffffff !important;
}
#gr_audiobook_player audio::-webkit-media-controls-panel {
width: 100% !important;
padding-top: 10px !important;
padding-bottom: 10px !important;
border-radius: 0px !important;
background-color: #ebedf0 !important;
color: #ffffff !important;
}
#gr_voice_player_hidden {
z-index: -100 !important;
position: absolute !important;
overflow: hidden !important;
margin: 0 !important;
padding: 0 !important;
width: 60px !important;
height: 60px !important;
}
#gr_state_update, #gr_restore_session, #gr_save_session,
#gr_audiobook_vtt, #gr_playback_time {
display: none !important;
}
///////////
.fade-in {
animation: fadeIn 1s ease-in !important;
display: inline-block !important;
}
@keyframes fadeIn {
from {
opacity: 0;
visibility: visible !important;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
}
//////////
#custom-gr-modal-container,
#custom-gr-modal-container .gr-modal {
position: fixed !important;
}
.hide-elem {
z-index: -1 !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
}
.gr-modal {
position: fixed !important;
top: 0 !important; left: 0 !important;
width: 100% !important; height: 100% !important;
background-color: rgba(0, 0, 0, 0.5) !important;
z-index: 9999 !important;
display: flex !important;
justify-content: center !important;
align-items: center !important;
}
.gr-modal-content {
background-color: #333 !important;
padding: 20px !important;
border-radius: 9px !important;
text-align: center !important;
max-width: 300px !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5) !important;
border: 2px solid #FFA500 !important;
color: white !important;
position: relative !important;
}
.confirm-buttons {
display: flex !important;
justify-content: space-evenly !important;
margin-top: 20px !important;
}
.confirm-buttons button {
padding: 10px 20px !important;
border: none !important;
border-radius: 6px !important;
font-size: 16px !important;
cursor: pointer !important;
}
.button-green { background-color: #28a745 !important; color: white !important; }
.button-green:hover { background-color: #34d058 !important; }
.button-red { background-color: #dc3545 !important; color: white !important; }
.button-red:hover { background-color: #ff6f71 !important; }
.button-green:active, .button-red:active {
background: var(--body-text-color) !important;
color: var(--body-background-fill) !important;
}
.spinner {
margin: 15px auto !important;
border: 4px solid rgba(255, 255, 255, 0.2) !important;
border-top: 4px solid #FFA500 !important;
border-radius: 50% !important;
width: 30px !important;
height: 30px !important;
animation: spin 1s linear infinite !important;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
'''
with gr.Blocks(theme=theme, title=title, css=header_css, delete_cache=(604800, 86400)) as app:
with gr.Group(visible=True, elem_id='gr_group_main', elem_classes='gr-group-main') as gr_group_main:
with gr.Tabs(elem_id='gr_tabs'):
gr_tab_main = gr.Tab('Dashboard', elem_id='gr_tab_main', elem_classes='gr-tab')
with gr_tab_main:
with gr.Row(elem_id='gr_row_tab_main'):
with gr.Column(elem_id='gr_col_1', elem_classes=['gr-col'], scale=3):
with gr.Group(elem_id='gr_group_ebook_file', elem_classes=['gr-group']):
gr_import_markdown = gr.Markdown(elem_id='gr_import_markdown', elem_classes=['gr-markdown'], value='Import')
gr_ebook_file = gr.File(label=src_label_file, elem_id='gr_ebook_file', file_types=ebook_formats, file_count='single', allow_reordering=True, height=100)
gr_row_ebook_mode = gr.Row(elem_id='gr_row_ebook_mode')
with gr_row_ebook_mode:
gr_ebook_mode = gr.Dropdown(label='', elem_id='gr_ebook_mode', choices=[('File','single'), ('Directory','directory')], interactive=True, scale=2)
gr_chapters_preview = gr.Checkbox(label='Chapters Preview', elem_id='gr_chapters_preview', value=False, interactive=True, scale=1)
with gr.Group(elem_id='gr_group_language', elem_classes=['gr-group']):
gr_language_markdown = gr.Markdown(elem_id='gr_language_markdown', elem_classes=['gr-markdown'], value='Language')
gr_language = gr.Dropdown(label='', elem_id='gr_language', choices=language_options, value=default_language_code, type='value', interactive=True)
gr_group_voice_file = gr.Group(elem_id='gr_group_voice_file', elem_classes=['gr-group'], visible=visible_gr_group_voice_file)
with gr_group_voice_file:
gr_voice_markdown = gr.Markdown(elem_id='gr_voice_markdown', elem_classes=['gr-markdown'], value='Voices')
gr_voice_file = gr.File(label='Upload Voice', elem_id='gr_voice_file', file_types=voice_formats, value=None, height=100)
gr_row_voice_player = gr.Row(elem_id='gr_row_voice_player')
with gr_row_voice_player:
gr_voice_player_hidden = gr.Audio(elem_id='gr_voice_player_hidden', type='filepath', interactive=False, waveform_options=gr.WaveformOptions(show_recording_waveform=False), show_download_button=False, container=False, visible='hidden', show_share_button=True, show_label=False, scale=0, min_width=60)
gr_voice_play = gr.Button('', elem_id='gr_voice_play', elem_classes=['small-btn'], variant='secondary', interactive=True, visible=False, scale=0, min_width=60)
gr_voice_list = gr.Dropdown(label='Voices', elem_id='gr_voice_list', choices=voice_options, type='value', interactive=True, scale=2)
gr_voice_del_btn = gr.Button('🗑', elem_id='gr_voice_del_btn', elem_classes=['small-btn-red'], variant='secondary', interactive=True, visible=False, scale=0, min_width=60)
with gr.Group(elem_id='gr_group_device', elem_classes=['gr-group']):
gr_device_markdown = gr.Markdown(elem_id='gr_device_markdown', elem_classes=['gr-markdown'], value='Processor')
gr_device = gr.Dropdown(label='', elem_id='gr_device', choices=[(k, v["proc"]) for k, v in devices.items()], type='value', value=default_device, interactive=True)
with gr.Column(elem_id='gr_col_2', elem_classes=['gr-col'], scale=3):
with gr.Group(elem_id='gr_group_tts_engine', elem_classes=['gr-group']):
gr_tts_rating = gr.Markdown(elem_id='gr_tts_rating', elem_classes=['gr-markdown'], value='TTS Engine')
gr_tts_engine_list = gr.Dropdown(label='', elem_id='gr_tts_engine_list', choices=tts_engine_options, type='value', interactive=True)
with gr.Group(elem_id='gr_group_models', elem_classes=['gr-group']):
gr_models_markdown = gr.Markdown(elem_id='gr_models_markdown', elem_classes=['gr-markdown'], value='Models')
gr_fine_tuned_list = gr.Dropdown(label='Fine Tuned Preset Models', elem_id='gr_fine_tuned_list', choices=fine_tuned_options, type='value', interactive=True)
gr_group_custom_model = gr.Group(visible=visible_gr_group_custom_model)
with gr_group_custom_model:
gr_custom_model_label = gr.Textbox(label='', elem_id='gr_custom_model_label', elem_classes=['gr-label'], interactive=False)
gr_custom_model_file = gr.File(label=f"Upload ZIP File", elem_id='gr_custom_model_file', value=None, file_types=['.zip'], height=100)
gr_row_custom_model_list = gr.Row(elem_id='gr_row_custom_model_list')
with gr_row_custom_model_list:
gr_custom_model_list = gr.Dropdown(label='', elem_id='gr_custom_model_list', choices=custom_model_options, type='value', interactive=True, scale=2)
gr_custom_model_del_btn = gr.Button('🗑', elem_id='gr_custom_model_del_btn', elem_classes=['small-btn'], variant='secondary', interactive=True, visible=False, scale=0, min_width=60)
with gr.Group(elem_id='gr_group_output_format'):
gr_output_markdown = gr.Markdown(elem_id='gr_output_markdown', elem_classes=['gr-markdown'], value='Output')
with gr.Row(elem_id='gr_row_output_format'):
gr_output_format_list = gr.Dropdown(label='Format', elem_id='gr_output_format_list', choices=output_formats, type='value', value=default_output_format, interactive=True, scale=1)
gr_output_channel_list = gr.Dropdown(label='Channel', elem_id='gr_output_channel_list', choices=['mono', 'stereo'], type='value', value=default_output_channel, interactive=True, scale=1)
with gr.Group(elem_id='gr_group_output_split'):
gr_output_split = gr.Checkbox(label='Split File', elem_id='gr_output_split', value=default_output_split, interactive=True)
gr_row_output_split_hours = gr.Row(elem_id='gr_row_output_split_hours', visible=False)
with gr_row_output_split_hours:
gr_output_split_hours_markdown = gr.Markdown(elem_id='gr_output_split_hours_markdown',elem_classes=['gr-markdown-output-split-hours'], value='Hours<br/>/ Part')
gr_output_split_hours = gr.Dropdown(label='', elem_id='gr_output_split_hours', choices=options_output_split_hours, type='value', value=default_output_split_hours, interactive=True, scale=1)
with gr.Group(elem_id='gr_group_session', elem_classes=['gr-group']):
gr_session_markdown = gr.Markdown(elem_id='gr_session_markdown', elem_classes=['gr-markdown'], value='Session')
gr_session = gr.Textbox(label='', elem_id='gr_session', interactive=False)
gr_tab_xtts_params = gr.Tab('XTTSv2 Settings', elem_id='gr_tab_xtts_params', elem_classes='gr-tab', visible=visible_gr_tab_xtts_params)
with gr_tab_xtts_params:
with gr.Group(elem_id='gr_group_xtts_params', elem_classes=['gr-group']):
gr_xtts_temperature = gr.Slider(
label='Temperature',
minimum=0.05,
maximum=5.0,
step=0.05,
value=float(default_engine_settings[TTS_ENGINES['XTTSv2']]['temperature']),
elem_id='gr_xtts_temperature',
info='Higher values lead to more creative, unpredictable outputs. Lower values make it more monotone.'
)
gr_xtts_length_penalty = gr.Slider(
label='Length Penalty',
minimum=0.3,
maximum=5.0,
step=0.1,
value=float(default_engine_settings[TTS_ENGINES['XTTSv2']]['length_penalty']),
elem_id='gr_xtts_length_penalty',
info='Adjusts how much longer sequences are preferred. Higher values encourage the model to produce longer and more natural speech.',
visible=False
)
gr_xtts_num_beams = gr.Slider(
label='Number Beams',
minimum=1,
maximum=10,
step=1,
value=int(default_engine_settings[TTS_ENGINES['XTTSv2']]['num_beams']),
elem_id='gr_xtts_num_beams',
info='Controls how many alternative sequences the model explores. Higher values improve speech coherence and pronunciation but increase inference time.',
visible=False
)
gr_xtts_repetition_penalty = gr.Slider(
label='Repetition Penalty',
minimum=1.0,
maximum=5.0,
step=0.1,
value=float(default_engine_settings[TTS_ENGINES['XTTSv2']]['repetition_penalty']),
elem_id='gr_xtts_repetition_penalty',
info='Penalizes repeated phrases. Higher values reduce repetition.'
)
gr_xtts_top_k = gr.Slider(
label='Top-k Sampling',
minimum=10,
maximum=100,
step=1,
value=int(default_engine_settings[TTS_ENGINES['XTTSv2']]['top_k']),
elem_id='gr_xtts_top_k',
info='Lower values restrict outputs to more likely words and increase speed at which audio generates.'
)
gr_xtts_top_p = gr.Slider(
label='Top-p Sampling',
minimum=0.1,
maximum=1.0,
step=0.01,
value=float(default_engine_settings[TTS_ENGINES['XTTSv2']]['top_p']),
elem_id='gr_xtts_top_p',
info='Controls cumulative probability for word selection. Lower values make the output more predictable and increase speed at which audio generates.'
)
gr_xtts_speed = gr.Slider(
label='Speed',
minimum=0.5,
maximum=3.0,
step=0.1,
value=float(default_engine_settings[TTS_ENGINES['XTTSv2']]['speed']),
elem_id='gr_xtts_speed',
info='Adjusts how fast the narrator will speak.'
)
gr_xtts_enable_text_splitting = gr.Checkbox(
label='Enable Text Splitting',
value=default_engine_settings[TTS_ENGINES['XTTSv2']]['enable_text_splitting'],
elem_id='gr_xtts_enable_text_splitting',
info='Coqui-tts builtin text splitting. Can help against hallucinations bu can also be worse.',
visible=False
)
gr_tab_bark_params = gr.Tab('Bark Settings', elem_id='gr_tab_bark_params', elem_classes='gr-tab', visible=visible_gr_tab_bark_params)
with gr_tab_bark_params:
gr.Markdown(
elem_id='gr_markdown_tab_bark_params',
value='''
### Customize BARK Parameters
Adjust the settings below to influence how the audio is generated, emotional and voice behavior random or more conservative
'''
)
with gr.Group(elem_id='gr_group_bark_params', elem_classes=['gr-group']):
gr_bark_text_temp = gr.Slider(
label='Text Temperature',
minimum=0.0,
maximum=1.0,
step=0.01,
value=float(default_engine_settings[TTS_ENGINES['BARK']]['text_temp']),
elem_id='gr_bark_text_temp',
info='Higher values lead to more creative, unpredictable outputs. Lower values make it more conservative.'
)
gr_bark_waveform_temp = gr.Slider(
label='Waveform Temperature',
minimum=0.0,
maximum=1.0,
step=0.01,
value=float(default_engine_settings[TTS_ENGINES['BARK']]['waveform_temp']),
elem_id='gr_bark_waveform_temp',
info='Higher values lead to more creative, unpredictable outputs. Lower values make it more conservative.'
)
with gr.Group(elem_id='gr_group_progress', elem_classes=['gr-group-sides-padded']):
gr_progress_markdown = gr.Markdown(elem_id='gr_progress_markdown', elem_classes=['gr-markdown'], value='Status')
gr_progress = gr.Textbox(elem_id='gr_progress', label='', interactive=False, visible=True)
gr_group_audiobook_list = gr.Group(elem_id='gr_group_audiobook_list', elem_classes=['gr-group-sides-padded'], visible=True)
with gr_group_audiobook_list:
gr_audiobook_markdown = gr.Markdown(elem_id='gr_audiobook_markdown', elem_classes=['gr-markdown'], value='Audiobook')
gr_audiobook_vtt = gr.Textbox(elem_id='gr_audiobook_vtt', label='', interactive=False, visible='hidden')
gr_playback_time = gr.Number(elem_id="gr_playback_time", label='', interactive=False, visible='hidden', value=0.0)
gr_audiobook_sentence = gr.Textbox(elem_id='gr_audiobook_sentence', label='', value='...', interactive=False, lines=3, max_lines=3)
gr_audiobook_player = gr.Audio(elem_id='gr_audiobook_player', label='', type='filepath', autoplay=False, interactive=False, waveform_options=gr.WaveformOptions(show_recording_waveform=False), show_download_button=False, show_share_button=False, container=True, visible=True)
gr_row_audiobook_list = gr.Row(elem_id='gr_row_audiobook_list', visible=True)
with gr_row_audiobook_list:
gr_audiobook_download_btn = gr.Button(elem_id='gr_audiobook_download_btn', value='', elem_classes=['small-btn'], variant='secondary', interactive=True, scale=0, min_width=60)
gr_audiobook_list = gr.Dropdown(elem_id='gr_audiobook_list', label='', choices=audiobook_options, type='value', interactive=True, scale=2)
gr_audiobook_del_btn = gr.Button(elem_id='gr_audiobook_del_btn', value='🗑', elem_classes=['small-btn-red'], variant='secondary', interactive=True, scale=0, min_width=60)
gr_audiobook_files = gr.Files(label='', elem_id='gr_audiobook_files', visible=False)
gr_audiobook_files_toggled = gr.State(False)
with gr.Group(elem_id='gr_convert_btn', elem_classes=['gr-group-convert-btn']):
gr_convert_btn = gr.Button(elem_id='gr_convert_btn', value='📚', elem_classes='gr-convert-btn', variant='primary', interactive=False)
gr_version_markdown = gr.Markdown(elem_id='gr_version_markdown', value=f'''
<div style="right:0;margin:auto;padding:10px;text-align:center">
<a href="https://github.com/DrewThomasson/ebook2audiobook" style="text-decoration:none;font-size:14px" target="_blank">
<b>{title}</b>&nbsp;<b style="color:orange; text-shadow: 0.3px 0.3px 0.3px #303030">{prog_version}</b></a>
</div>
'''
)
with gr.Group(visible=False, elem_id='gr_group_blocks', elem_classes=['gr-group-main']) as gr_group_blocks:
gr.Markdown('### Confirm Blocks')
with gr.Group() as gr_group_blocks_content:
pass
with gr.Row():
gr_confirm_blocks_yes_btn = gr.Button(elem_id='gr_confirm_blocks_yes_btn', elem_classes=['hide-elem'], value='', variant='secondary', visible=True, scale=0, min_width=30)
gr_confirm_blocks_no_btn = gr.Button(elem_id='gr_confirm_blocks_no_btn', elem_classes=['hide-elem'], value='', variant='secondary', visible=True, scale=0, min_width=30)
gr_modal = gr.HTML(visible=False)
gr_glassmask = gr.HTML(gr_glassmask_msg, elem_id='gr_glassmask', elem_classes=['gr-glass-mask'])
gr_confirm_deletion_field_hidden = gr.Textbox(elem_id='confirm_hidden', visible=False)
gr_confirm_deletion_yes_btn = gr.Button(elem_id='gr_confirm_deletion_yes_btn', elem_classes=['hide-elem'], value='', variant='secondary', visible=True, scale=0, size='sm', min_width=0)
gr_confirm_deletion_no_btn = gr.Button(elem_id='gr_confirm_deletion_no_btn', elem_classes=['hide-elem'], value='', variant='secondary', visible=True, scale=0, size='sm', min_width=0)
gr_state_update = gr.State(value={'hash': None})
gr_restore_session = gr.JSON(elem_id='gr_restore_session', visible='hidden')
gr_save_session = gr.JSON(elem_id='gr_save_session', visible='hidden')
def disable_components()->tuple:
outputs = tuple([gr.update(interactive=False) for _ in range(12)])
return outputs
def enable_components(id:str)->tuple:
session = context.get_session(id)
if session:
if session['event'] == 'confirm_blocks':
outputs = tuple([gr.update() for _ in range(12)])
return outputs
outputs = tuple([gr.update(interactive=True) for _ in range(12)])
return outputs
def show_gr_modal(type:str, msg:str)->str:
return f'''
<div id="custom-gr_modal" class="gr-modal">
<div class="gr-modal-content">
<p style="color:#ffffff">{msg[:70]}...</p>
{show_confirm_buttons(type)}
</div>
</div>
'''
def show_confirm_buttons(mode:str)->str:
if mode in ['confirm_deletion', 'confirm_blocks']:
button_yes = f'#gr_{mode}_yes_btn'
button_no = f'#gr_{mode}_no_btn'
return f'''
<div class="confirm-buttons">
<button class="button-green" onclick="document.querySelector('{button_yes}').click()">✔</button>
<button class="button-red" onclick="document.querySelector('{button_no}').click()">⨉</button>
</div>
'''
else:
return '<div class="spinner"></div>'
def show_rating(tts_engine:str)->str:
def yellow_stars(n:int):
return "".join(
"<span style='color:#f0bc00; font-size:12px'>★</span>" for _ in range(n)
)
def color_box(value:int)->str:
if value <= 4:
color = "#4CAF50" # Green = low
elif value <= 8:
color = "#FF9800" # Orange = medium
else:
color = "#F44336" # Red = high
return f"<span style='background:{color};color:white; padding: 0 3px 0 3px; border-radius:3px; font-size:11px; white-space: nowrap'>{str(value)} GB</span>"
rating = default_engine_settings[tts_engine]['rating']
return f'''
<div style="display:flex; justify-content:space-between; align-items:flex-end;">
<span class="gr-markdown-span">TTS Engine</span>
<table style="
display:inline-block;
border-collapse:collapse;
border:none;
margin:0;
padding:0;
font-size:12px;
line-height:1.2; /* compact, but no clipping */
">
<tr style="border:none; vertical-align:bottom;">
<td style="padding:0 5px 0 2.5px; border:none; vertical-align:bottom;">
<b>VRAM:</b> {color_box(int(rating["VRAM"]))}
</td>
<td style="padding:0 5px 0 2.5px; border:none; vertical-align:bottom;">
<b>CPU:</b> {yellow_stars(int(rating["CPU"]))}
</td>
<td style="padding:0 5px 0 2.5px; border:none; vertical-align:bottom;">
<b>RAM:</b> {color_box(int(rating["RAM"]))}
</td>
<td style="padding:0 5px 0 2.5px; border:none; vertical-align:bottom;">
<b>Realism:</b> {yellow_stars(int(rating["Realism"]))}
</td>
</tr>
</table>
</div>
'''
def restore_interface(id:str, req:gr.Request)->tuple:
try:
session = context.get_session(id)
if session:
socket_hash = str(req.session_hash)
if not session.get(socket_hash):
outputs = tuple([gr.update() for _ in range(15)])
return outputs
ebook_data = None
file_count = session['ebook_mode']
if session['ebook_list'] is not None and file_count == 'directory':
session['ebook'] = None
ebook_data = [f for f in session["ebook_list"] if os.path.exists(f)]
if not ebook_data:
ebook_data = None
elif isinstance(session['ebook'], str) and file_count == 'single':
session['ebook_list'] = None
if os.path.exists(session['ebook']):
ebook_data = session['ebook']
else:
ebook_data = session['ebook'] = None
else:
ebook_data = session['ebook'] = None
if ebook_data is not None:
current_dir_cache = tempfile.gettempdir()
current_dir_cache_norm = os.path.normpath(current_dir_cache)
prev_cache_dir = os.path.normpath(os.path.dirname(ebook_data[0]) if isinstance(ebook_data, list) else os.path.dirname(ebook_data))
if prev_cache_dir != current_dir_cache_norm:
ebook_data = None
session['ebook'] = ebook_data
visible_row_split_hours = True if session['output_split'] else False
return (
gr.update(value=ebook_data),
gr.update(value=session['ebook_mode']),
gr.update(value=bool(session['chapters_preview'])),
gr.update(value=session['device']),
gr.update(value=session['language']),
update_gr_voice_list(id),
update_gr_tts_engine_list(id),
update_gr_custom_model_list(id),
update_gr_fine_tuned_list(id),
gr.update(value=session['output_format']),
gr.update(value=session['output_channel']),
gr.update(value=bool(session['output_split'])),
gr.update(value=session['output_split_hours']),
gr.update(visible=visible_row_split_hours),
update_gr_audiobook_list(id)
)
except Exception as e:
error = f'restore_interface(): {e}'
alert_exception(error, id)
outputs = tuple([gr.update() for _ in range(15)])
return outputs
def restore_audiobook_player(audiobook:str|None)->tuple:
try:
visible = True if audiobook is not None else False
return gr.update(visible=visible), gr.update(value=audiobook), gr.update(active=True)
except Exception as e:
error = f'restore_audiobook_player(): {e}'
alert_exception(error, None)
outputs = tuple([gr.update() for _ in range(3)])
return outputs
def refresh_interface(id:str)->tuple:
session = context.get_session(id)
if session:
if session['event'] == 'confirm_blocks':
outputs = tuple([gr.update() for _ in range(9)])
return outputs
else:
return (
gr.update(interactive=False), gr.update(value=None), gr.update(value=session['device']), update_gr_audiobook_list(id),
gr.update(value=session['audiobook']), gr.update(visible=False), update_gr_voice_list(id), gr.update(value='')
)
outputs = tuple([gr.update() for _ in range(8)])
return outputs
def change_gr_audiobook_list(selected:str|None, id:str)->dict:
try:
session = context.get_session(id)
if session:
session['audiobook'] = selected
group_visible = True if len(audiobook_options) > 0 else False
return gr.update(visible=group_visible)
except Exception as e:
error = f'change_gr_audiobook_list(): {e}'
alert_exception(error, id)
return gr.update(visible=group_visible)
def update_gr_audiobook_player(id:str)->tuple:
try:
session = context.get_session(id)
if session:
if session['audiobook'] is not None:
vtt = Path(session['audiobook']).with_suffix('.vtt')
if not os.path.exists(session['audiobook']) or not os.path.exists(vtt):
error = f"{Path(session['audiobook']).name} does not exist!"
print(error)
alert_exception(error, id)
return gr.update(value=0.0), gr.update(value=None), gr.update(value=None)
audio_info = mediainfo(session['audiobook'])
duration = audio_info.get('duration', False)
if duration:
session['duration'] = float(audio_info['duration'])
with open(vtt, "r", encoding="utf-8-sig", errors="replace") as f:
vtt_content = f.read()
return gr.update(value=0.0), gr.update(value=session['audiobook']), gr.update(value=vtt_content)
else:
error = f"{Path(session['audiobook']).name} corrupted or not encoded!"
print(error)
alert_exception(error, id)
except Exception as e:
error = f'update_gr_audiobook_player(): {e}'
print(error)
alert_exception(error, id)
return gr.update(value=0.0), gr.update(value=None), gr.update(value=None)
def update_gr_glassmask(str:str=gr_glassmask_msg, attr:list=['gr-glass-mask'])->dict:
return gr.update(value=str, elem_id='gr_glassmask', elem_classes=attr)
def change_convert_btn(upload_file:str|None=None, upload_file_mode:str|None=None, custom_model_file:str|None=None, session:DictProxy=None)->dict:
try:
if session is None:
return gr.update(variant='primary', interactive=False)
else:
if hasattr(upload_file, 'name') and not hasattr(custom_model_file, 'name'):
return gr.update(variant='primary', interactive=True)
elif isinstance(upload_file, list) and len(upload_file) > 0 and upload_file_mode == 'directory' and not hasattr(custom_model_file, 'name'):
return gr.update(variant='primary', interactive=True)
else:
return gr.update(variant='primary', interactive=False)
except Exception as e:
error = f'change_convert_btn(): {e}'
alert_exception(error, None)
gr.update()
def change_gr_ebook_file(data:str|None, id:str)->tuple:
try:
session = context.get_session(id)
if session:
session["ebook"] = None
session["ebook_list"] = None
if data is None:
if session.get("status") == "converting":
session["cancellation_requested"] = True
msg = "Cancellation requested, please wait..."
yield gr.update(value=show_gr_modal("wait", msg), visible=True)
return
if isinstance(data, list):
ebook_list = []
for f in data:
path = f.get("path") if isinstance(f, dict) else str(f)
ebook_list.append(path)
session["ebook_list"] = ebook_list
else:
session["ebook"] = data
session["cancellation_requested"] = False
return gr.update(value='', visible=False)
except Exception as e:
error = f'change_gr_ebook_file(): {e}'
alert_exception(error, id)
return gr.update(value='', visible=False)
def change_gr_ebook_mode(val:str, id:str)->tuple:
session = context.get_session(id)
if session:
session['ebook_mode'] = val
if val == 'single':
return gr.update(label=src_label_file, file_count='single'), gr.update(visible=True)
else:
return gr.update(label=src_label_dir, file_count='directory'), gr.update(visible=False)
return gr.update(), gr.update()
def change_gr_voice_file(f:str|None, id:str)->tuple:
state = {}
if f is not None:
if len(voice_options) > max_custom_voices:
error = f'You are allowed to upload a max of {max_custom_voices} voices'
state['type'] = 'warning'
state['msg'] = error
elif os.path.splitext(f.name)[1] not in voice_formats:
error = f'The audio file format selected is not valid.'
state['type'] = 'warning'
state['msg'] = error
else:
session = context.get_session(id)
if session:
voice_name = os.path.splitext(os.path.basename(f))[0].replace('&', 'And')
voice_name = get_sanitized(voice_name)
final_voice_file = os.path.join(session['voice_dir'], f'{voice_name}.wav')
extractor = VoiceExtractor(session, f, voice_name)
status, msg = extractor.extract_voice()
if status:
session['voice'] = final_voice_file
msg = f'Voice {voice_name} added to the voices list'
state['type'] = 'success'
state['msg'] = msg
show_alert(state)
return update_gr_voice_list(id)
else:
error = 'failed! Check if you audio file is compatible.'
state['type'] = 'warning'
state['msg'] = error
show_alert(state)
return gr.update()
def change_gr_voice_list(selected:str|None, id:str)->tuple:
session = context.get_session(id)
if session:
if not voice_options:
session['voice'] = None
else:
voice_value = voice_options[0][1]
session['voice'] = next(
(value for label, value in voice_options if value == selected),
voice_value,
)
visible = session['voice'] is not None
return gr.update(value=session['voice']), gr.update(visible=visible), gr.update(visible=visible)
return gr.update(), gr.update(), gr.update()
def click_gr_voice_del_btn(selected:str, id:str)->tuple:
try:
if selected is not None:
session = context.get_session(id)
if session:
speaker_path = os.path.abspath(selected)
speaker = re.sub(r'\.wav$|\.npz|\.pth$', '', os.path.basename(selected))
builtin_root = os.path.join(voices_dir, session['language'])
is_in_builtin = os.path.commonpath([
speaker_path,
os.path.abspath(builtin_root)
]) == os.path.abspath(builtin_root)
is_in_models = os.path.commonpath([
speaker_path,
os.path.abspath(session['custom_model_dir'])
]) == os.path.abspath(session['custom_model_dir'])
# Check if voice is built-in
is_builtin = any(
speaker in settings.get('voices', {})
for settings in (default_engine_settings[engine] for engine in TTS_ENGINES.values())
)
if is_builtin and is_in_builtin:
error = f'Voice file {speaker} is a builtin voice and cannot be deleted.'
show_alert({"type": "warning", "msg": error})
return gr.update(), gr.update(visible=False)
if is_in_models:
error = f'Voice file {speaker} is a voice of one of your custom model and cannot be deleted.'
show_alert({"type": "warning", "msg": error})
return gr.update(), gr.update(visible=False)
try:
selected_path = Path(selected).resolve()
parent_path = Path(session['voice_dir']).parent.resolve()
if parent_path in selected_path.parents:
msg = f'Are you sure to delete {speaker}...'
return (
gr.update(value='confirm_voice_del'),
gr.update(value=show_gr_modal('confirm_deletion', msg), visible=True)
)
else:
error = f'{speaker} is part of the global voices directory. Only your own custom uploaded voices can be deleted!'
show_alert({"type": "warning", "msg": error})
return gr.update(), gr.update(visible=False)
except Exception as e:
error = f'Could not delete the voice file {selected}!\n{e}'
alert_exception(error, id)
return gr.update(), gr.update(visible=False)
# Fallback/default return if not selected or after errors
return gr.update(), gr.update(visible=False)
except Exception as e:
error = f'click_gr_voice_del_btn(): {e}'
alert_exception(error, id)
return gr.update(), gr.update(visible=False)
def click_gr_custom_model_del_btn(selected:str, id:str)->tuple:
try:
if selected is not None:
session = context.get_session(id)
if session:
selected_name = os.path.basename(selected)
msg = f'Are you sure to delete {selected_name}...'
return gr.update(value='confirm_custom_model_del'), gr.update(value=show_gr_modal('confirm_deletion', msg), visible=True)
except Exception as e:
error = f'Could not delete the custom model {selected_name}!'
alert_exception(error, id)
return gr.update(), gr.update(visible=False)
def click_gr_audiobook_del_btn(selected:str, id:str)->tuple:
try:
if selected is not None:
session = context.get_session(id)
if session:
selected_name = Path(selected).stem
msg = f'Are you sure to delete {selected_name}...'
return gr.update(value='confirm_audiobook_del'), gr.update(value=show_gr_modal('confirm_deletion', msg), visible=True)
except Exception as e:
error = f'Could not delete the audiobook {selected_name}!'
alert_exception(error, id)
return gr.update(), gr.update(visible=False), gr.update(visible=False)
def confirm_deletion(voice_path:str, custom_model:str, audiobook:str, id:str, method:str|None=None)->tuple:
try:
nonlocal models
if method is not None:
session = context.get_session(id)
if session:
models = load_engine_presets(session['tts_engine'])
if method == 'confirm_voice_del':
selected_name = Path(voice_path).stem
pattern = re.sub(r'\.wav$', '*.wav', voice_path)
files2remove = glob(pattern)
for file in files2remove:
os.remove(file)
shutil.rmtree(os.path.join(os.path.dirname(voice_path), 'bark', selected_name), ignore_errors=True)
msg = f"Voice file {re.sub(r'.wav$', '', selected_name)} deleted!"
session['voice'] = None
show_alert({"type": "warning", "msg": msg})
return gr.update(), gr.update(), gr.update(value='', visible=False), update_gr_voice_list(id)
elif method == 'confirm_custom_model_del':
selected_name = os.path.basename(custom_model)
shutil.rmtree(custom_model, ignore_errors=True)
msg = f'Custom model {selected_name} deleted!'
if session['custom_model'] is not None and session['voice'] is not None:
if session['custom_model'] in session['voice']:
session['voice'] = models[session['fine_tuned']]['voice']
session['custom_model'] = None
show_alert({"type": "warning", "msg": msg})
return update_gr_custom_model_list(id), gr.update(), gr.update(value='', visible=False), gr.update()
elif method == 'confirm_audiobook_del':
selected_name = Path(audiobook).stem
if os.path.isdir(audiobook):
shutil.rmtree(selected, ignore_errors=True)
elif os.path.exists(audiobook):
os.remove(audiobook)
vtt_path = Path(audiobook).with_suffix('.vtt')
if os.path.exists(vtt_path):
os.remove(vtt_path)
process_dir = os.path.join(session['session_dir'], f"{hashlib.md5(os.path.join(session['audiobooks_dir'], audiobook).encode()).hexdigest()}")
shutil.rmtree(process_dir, ignore_errors=True)
msg = f'Audiobook {selected_name} deleted!'
session['audiobook'] = None
show_alert({"type": "warning", "msg": msg})
return gr.update(), update_gr_audiobook_list(id), gr.update(value='', visible=False), gr.update()
except Exception as e:
error = f'confirm_deletion(): {e}!'
alert_exception(error, id)
return gr.update(), gr.update(), gr.update(value='', visible=False), gr.update()
def confirm_blocks(choice:str, id:str)->dict:
session = context.get_session(id)
if session:
if choice == 'yes':
session['event'] = 'blocks_confirmed'
else:
session['status'] = 'ready'
return gr.update(value='', visible=False)
def update_gr_voice_list(id:str)->dict:
try:
nonlocal voice_options
nonlocal models
session = context.get_session(id)
if session:
models = load_engine_presets(session['tts_engine'])
lang_dir = session['language'] if session['language'] != 'con' else 'con-' # Bypass Windows CON reserved name
file_pattern = "*.wav"
eng_options = []
bark_options = []
builtin_dir = Path(os.path.join(voices_dir, lang_dir))
builtin_options = [
(base, str(f))
for f in builtin_dir.rglob(file_pattern)
for base in [os.path.splitext(f.name)[0]]
]
builtin_names = {t[0]: None for t in builtin_options}
if session['language'] in default_engine_settings[TTS_ENGINES['XTTSv2']].get('languages', {}):
eng_dir = Path(os.path.join(voices_dir, "eng"))
eng_options = [
(base, str(f))
for f in eng_dir.rglob(file_pattern)
for base in [os.path.splitext(f.name)[0]]
if base not in builtin_names
]
if session['tts_engine'] == TTS_ENGINES['BARK']:
lang_dict = Lang(session['language'])
if lang_dict:
lang_iso1 = lang_dict.pt1
lang = lang_iso1.lower()
speakers_path = Path(default_engine_settings[TTS_ENGINES['BARK']]['speakers_path'])
pattern_speaker = re.compile(r"^.*?_speaker_(\d+)$")
bark_options = [
(pattern_speaker.sub(r"Speaker \1", f.stem), str(f.with_suffix(".wav")))
for f in speakers_path.rglob(f"{lang}_speaker_*.npz")
]
voice_options = builtin_options + eng_options + bark_options
session['voice_dir'] = os.path.join(voices_dir, '__sessions', f"voice-{session['id']}", session['language'])
os.makedirs(session['voice_dir'], exist_ok=True)
if session['voice_dir'] is not None:
session_voice_dir = Path(session['voice_dir'])
voice_options += [
(os.path.splitext(f.name)[0], str(f))
for f in session_voice_dir.rglob(file_pattern)
if f.is_file()
]
if session.get('custom_model_dir'):
voice_options.extend(
(f.stem, str(f))
for f in Path(session['custom_model_dir']).rglob('*.wav')
if f.is_file()
)
if session['tts_engine'] in [TTS_ENGINES['VITS'], TTS_ENGINES['FAIRSEQ'], TTS_ENGINES['TACOTRON2'], TTS_ENGINES['YOURTTS']]:
voice_options = [('Default', None)] + sorted(voice_options, key=lambda x: x[0].lower())
else:
voice_options = sorted(voice_options, key=lambda x: x[0].lower())
if session['voice'] is not None and isinstance(session.get('voice'), str):
if session['voice_dir'] not in session['voice']:
if not any(v[1] == session['voice'] for v in voice_options):
voice_path = Path(session['voice'])
parts = list(voice_path.parts)
if "voices" in parts:
idx = parts.index("voices")
if idx + 1 < len(parts):
parts[idx + 1] = session['language']
new_voice_path = str(Path(*parts))
if os.path.exists(new_voice_path) and any(v[1] == new_voice_path for v in voice_options):
session['voice'] = new_voice_path
else:
parts[idx + 1] = 'eng'
session['voice'] = str(Path(*parts))
else:
if voice_options and voice_options[0][1] is not None:
session['voice'] = models[session['fine_tuned']]['voice']
return gr.update(choices=voice_options, value=session['voice'])
except Exception as e:
error = f'update_gr_voice_list(): {e}!'
alert_exception(error, id)
return gr.update()
def update_gr_tts_engine_list(id:str)->dict:
try:
nonlocal tts_engine_options
session = context.get_session(id)
if session:
tts_engine_options = get_compatible_tts_engines(session['language'])
session['tts_engine'] = session['tts_engine'] if session['tts_engine'] in tts_engine_options else tts_engine_options[0]
return gr.update(choices=tts_engine_options, value=session['tts_engine'])
except Exception as e:
error = f'update_gr_tts_engine_list(): {e}!'
alert_exception(error, id)
return gr.update()
def update_gr_custom_model_list(id:str)->dict:
try:
nonlocal custom_model_options
session = context.get_session(id)
if session:
custom_model_tts_dir = check_custom_model_tts(session['custom_model_dir'], session['tts_engine'])
custom_model_options = [('None', None)] + [
(
str(dir),
os.path.join(custom_model_tts_dir, dir)
)
for dir in os.listdir(custom_model_tts_dir)
if os.path.isdir(os.path.join(custom_model_tts_dir, dir))
]
session['custom_model'] = session['custom_model'] if session['custom_model'] in [option[1] for option in custom_model_options] else custom_model_options[0][1]
model_paths = {v[1] for v in custom_model_options}
return gr.update(choices=custom_model_options, value=session['custom_model'])
except Exception as e:
error = f'update_gr_custom_model_list(): {e}!'
alert_exception(error, id)
return gr.update()
def update_gr_fine_tuned_list(id:str)->dict:
try:
nonlocal fine_tuned_options
nonlocal models
session = context.get_session(id)
if session:
models = load_engine_presets(session['tts_engine'])
fine_tuned_options = [
name
for name, details in models.items()
if details.get("lang") in ("multi", session["language"])
]
if session['fine_tuned'] in fine_tuned_options:
fine_tuned = session['fine_tuned']
else:
fine_tuned = default_fine_tuned
session['fine_tuned'] = fine_tuned
return gr.update(choices=fine_tuned_options, value=session['fine_tuned'])
except Exception as e:
error = f'update_gr_fine_tuned_list(): {e}!'
alert_exception(error, id)
return gr.update()
def change_gr_device(selected:str, id:str)->None:
session = context.get_session(id)
if session:
session['device'] = selected
def change_gr_language(selected:str, id:str)->tuple:
if selected:
session = context.get_session(id)
if session:
prev = session['language']
session['language'] = selected
return (
gr.update(value=session['language']),
update_gr_tts_engine_list(id),
update_gr_custom_model_list(id),
update_gr_fine_tuned_list(id),
update_gr_voice_list(id)
)
return gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
def check_custom_model_tts(custom_model_dir:str, tts_engine:str)->str|None:
dir_path = None
if custom_model_dir is not None and tts_engine is not None:
dir_path = os.path.join(custom_model_dir, tts_engine)
if not os.path.isdir(dir_path):
os.makedirs(dir_path, exist_ok=True)
return dir_path
def change_gr_custom_model_file(f:str|None, t:str, id:str)->tuple:
nonlocal models
if f is not None:
state = {}
if len(custom_model_options) > max_custom_model:
error = f'You are allowed to upload a max of {max_custom_models} models'
state['type'] = 'warning'
state['msg'] = error
else:
session = context.get_session(id)
if session:
models = load_engine_presets(session['tts_engine'])
session['tts_engine'] = t
if analyze_uploaded_file(f, models['internal']['files']):
model = extract_custom_model(f, id, models[default_fine_tuned]['files'])
if model is not None:
session['custom_model'] = model
session['voice'] = os.path.join(model, f'{os.path.basename(os.path.normpath(model))}.wav')
msg = f'{os.path.basename(model)} added to the custom models list'
state['type'] = 'success'
state['msg'] = msg
show_alert(state)
return gr.update(value=None), update_gr_custom_model_list(id), update_gr_voice_list(id)
else:
error = f'Cannot extract custom model zip file {os.path.basename(f)}'
state['type'] = 'warning'
state['msg'] = error
else:
error = f'{os.path.basename(f)} is not a valid model or some required files are missing'
state['type'] = 'warning'
state['msg'] = error
show_alert(state)
return gr.update(), gr.update(), gr.update()
def change_gr_tts_engine_list(engine:str, id:str)->tuple:
nonlocal models
session = context.get_session(id)
if session:
models = load_engine_presets(engine)
session['tts_engine'] = engine
session['fine_tuned'] = default_fine_tuned
session['voice'] = None if engine not in [TTS_ENGINES['XTTSv2'], TTS_ENGINES['BARK']] else session['voice']
bark_visible = False
if session['tts_engine'] == TTS_ENGINES['XTTSv2']:
visible_custom_model = True if session['fine_tuned'] == 'internal' else False
return (
gr.update(value=show_rating(session['tts_engine'])),
gr.update(visible=visible_gr_tab_xtts_params),
gr.update(visible=False),
gr.update(visible=visible_custom_model),
update_gr_fine_tuned_list(id),
gr.update(label=f"Upload {session['tts_engine']} ZIP file (Mandatory: {', '.join(models[default_fine_tuned]['files'])})"),
update_gr_voice_list(id),
gr.update(value=f"My {session['tts_engine']} Custom Models")
)
else:
if session['tts_engine'] == TTS_ENGINES['BARK']:
bark_visible = visible_gr_tab_bark_params
return (
gr.update(value=show_rating(session['tts_engine'])),
gr.update(visible=visible_gr_tab_bark_params),
gr.update(visible=bark_visible),
gr.update(visible=False),
update_gr_fine_tuned_list(id),
gr.update(label=f"*Upload Custom Model not available for {session['tts_engine']}"),
update_gr_voice_list(id),
gr.update(value='')
)
outputs = tuple([gr.update(interactive=False) for _ in range(8)])
return outputs
def change_gr_fine_tuned_list(selected:str, id:str)->tuple:
if selected:
session = context.get_session(id)
if session:
session['fine_tuned'] = selected
if selected == 'internal':
visible_custom_model = visible_gr_group_custom_model
else:
visible_custom_model = False
return gr.update(visible=visible_custom_model), update_gr_voice_list(id)
return gr.update(), gr.update()
def change_gr_custom_model_list(selected:str|None, id:str)->tuple:
session = context.get_session(id)
if session:
session['custom_model'] = selected
if selected is not None:
session['voice'] = os.path.join(selected, f"{os.path.basename(selected)}.wav")
visible_fine_tuned = True if selected is None else False
visible_del_btn = False if selected is None else True
return gr.update(visible=visible_fine_tuned), gr.update(visible=visible_del_btn), update_gr_voice_list(id)
return gr.update(), gr.update(), gr.update()
def change_gr_output_format_list(val:str, id:str)->None:
session = context.get_session(id)
if session:
session['output_format'] = val
return
def change_gr_output_channel_list(val:str, id:str)->None:
session = context.get_session(id)
if session:
session['output_channel'] = val
return
def change_gr_output_split(val:str, id:str)->dict:
session = context.get_session(id)
if session:
session['output_split'] = val
return gr.update(visible=val)
def change_gr_playback_time(time:float, id:str)->None:
session = context.get_session(id)
if session:
session['playback_time'] = time
return
def toggle_audiobook_files(audiobook:str, is_visible:bool)->tuple:
if not audiobook:
error = 'No audiobook selected.'
alert_exception(error, None)
return gr.update(), False
if is_visible:
return gr.update(visible=False, value=None), False
p = Path(audiobook)
if not p.exists():
error = f'Audio not found: {p}'
alert_exception(error, None)
return gr.update(), False
files = [str(p)]
vtt = p.with_suffix(".vtt")
if vtt.exists():
files.append(str(vtt))
return gr.update(visible=True, value=files), True
def change_param(key:str, val:Any, id:str, val2:Any=None)->None:
session = context.get_session(id)
if session:
session[key] = val
state = {}
if key == "chapters_preview":
msg = 'Chapters preview feature will be available to the next version'
state['type'] = 'info'
state['msg'] = msg
show_alert(state)
elif key == 'xtts_length_penalty':
if val2 is not None:
if float(val) > float(val2):
error = 'Length penalty must be always lower than num beams if greater than 1.0 or equal if 1.0'
state['type'] = 'warning'
state['msg'] = error
show_alert(state)
elif key == 'xtts_num_beams':
if val2 is not None:
if float(val) < float(val2):
error = 'Num beams must be always higher than length penalty or equal if its value is 1.0'
state['type'] = 'warning'
state['msg'] = error
show_alert(state)
def submit_convert_btn(
id:str, device:str, ebook_file:str, chapters_preview:bool, tts_engine:str, language:str, voice:str, custom_model:str, fine_tuned:str, output_format:str, output_channel:str, xtts_temperature:float,
xtts_length_penalty:int, xtts_num_beams:int, xtts_repetition_penalty:float, xtts_top_k:int, xtts_top_p:float, xtts_speed:float, xtts_enable_text_splitting:bool, bark_text_temp:float, bark_waveform_temp:float,
output_split:bool, output_split_hours:str
)->tuple:
try:
session = context.get_session(id)
if session:
args = {
"is_gui_process": session['is_gui_process'],
"session": id,
"script_mode": script_mode,
"chapters_preview": chapters_preview,
"device": device,
"tts_engine": tts_engine,
"ebook": ebook_file if isinstance(ebook_file, str) else None,
"ebook_list": ebook_file if isinstance(ebook_file, list) else None,
"audiobooks_dir": session['audiobooks_dir'],
"voice": voice,
"language": language,
"custom_model": custom_model,
"fine_tuned": fine_tuned,
"output_format": output_format,
"output_channel": output_channel,
"xtts_temperature": float(xtts_temperature),
"xtts_length_penalty": float(xtts_length_penalty),
"xtts_num_beams": int(session['xtts_num_beams']),
"xtts_repetition_penalty": float(xtts_repetition_penalty),
"xtts_top_k": int(xtts_top_k),
"xtts_top_p": float(xtts_top_p),
"xtts_speed": float(xtts_speed),
"xtts_enable_text_splitting": bool(xtts_enable_text_splitting),
"bark_text_temp": float(bark_text_temp),
"bark_waveform_temp": float(bark_waveform_temp),
"output_split": bool(output_split),
"output_split_hours": output_split_hours,
"event": None
}
error = None
if args['ebook'] is None and args['ebook_list'] is None:
error = 'Error: a file or directory is required.'
show_alert({"type": "warning", "msg": error})
elif args['xtts_num_beams'] < args['xtts_length_penalty']:
error = 'Error: num beams must be greater or equal than length penalty.'
show_alert({"type": "warning", "msg": error})
else:
session['status'] = 'converting'
session['progress'] = len(audiobook_options)
if isinstance(args['ebook_list'], list):
args['chapters_preview'] = None
ebook_list = args['ebook_list'][:]
for file in ebook_list:
if any(file.endswith(ext) for ext in ebook_formats):
print(f'Processing eBook file: {os.path.basename(file)}')
args['ebook'] = file
progress_status, passed = convert_ebook(args)
if passed is False:
if session['status'] == 'converting':
error = 'Conversion cancelled.'
break
else:
error = 'Conversion failed.'
break
else:
show_alert({"type": "success", "msg": progress_status})
args['ebook_list'].remove(file)
reset_session(args['session'])
count_file = len(args['ebook_list'])
if count_file > 0:
msg = f"{os.path.basename(file)} / converted. {len(args['ebook_list'])} ebook(s) conversion remaining..."
yield gr.update(value=msg), gr.update()
else:
msg = 'Conversion successful!'
session['status'] = 'ready'
return gr.update(value=msg), gr.update()
else:
print(f"Processing eBook file: {os.path.basename(args['ebook'])}")
progress_status, passed = convert_ebook(args)
if passed is False:
if session['status'] == 'converting':
error = 'Conversion cancelled.'
else:
error = 'Conversion failed.'
else:
if progress_status == 'confirm_blocks':
session['event'] = progress_status
msg = 'Select the blocks to convert:'
print(msg)
yield gr.update(value=''), gr.update(value=show_gr_modal(progress_status, msg), visible=True)
return
else:
show_alert({"type": "success", "msg": progress_status})
reset_session(args['session'])
msg = 'Conversion successful!'
session['status'] = 'ready'
return gr.update(value=msg), gr.update()
if error is not None:
show_alert({"type": "warning", "msg": error})
session['status'] = 'ready'
except Exception as e:
error = f'submit_convert_btn(): {e}'
alert_exception(error, id)
session['status'] = 'ready'
return gr.update(), gr.update()
def submit_confirmed_blocks(id:str)->tuple:
try:
session = context.get_session(id)
if session:
error = None
if isinstance(session['ebook_list'], list):
ebook_list = session['ebook_list'][:]
for file in ebook_list:
if any(file.endswith(ext) for ext in ebook_formats):
print(f'Processing eBook file: {os.path.basename(file)}')
session['ebook'] = file
progress_status, passed = convert_ebook(session)
if passed is False:
if session['status'] == 'converting':
error = 'Conversion cancelled.'
break
else:
error = 'Conversion failed.'
break
else:
show_alert({"type": "success", "msg": progress_status})
session['ebook_list'].remove(file)
reset_session(session['id'])
msg = 'Conversion successful!'
session['status'] = 'ready'
return gr.update(value=msg), gr.update()
else:
print(f"Processing eBook file: {os.path.basename(session['ebook'])}")
progress_status, passed = convert_ebook(session)
if passed is False:
if session['status'] == 'converting':
error = 'Conversion cancelled.'
else:
error = 'Conversion failed.'
session['status'] = 'ready'
else:
show_alert({"type": "success", "msg": progress_status})
reset_session(session['id'])
msg = 'Conversion successful!'
session['status'] = 'ready'
return gr.update(value=msg), gr.update()
if error is not None:
show_alert({"type": "warning", "msg": error})
except Exception as e:
error = f'submit_confirmed_blocks(): {e}'
alert_exception(error, id)
return gr.update(), gr.update()
def update_gr_audiobook_list(id:str)->dict:
try:
nonlocal audiobook_options
session = context.get_session(id)
if session:
if session['audiobooks_dir'] is not None:
audiobook_options = [
(f, os.path.join(session['audiobooks_dir'], str(f)))
for f in os.listdir(session['audiobooks_dir'])
if not f.lower().endswith(".vtt")
]
audiobook_options.sort(
key=lambda x: os.path.getmtime(x[1]),
reverse=True
)
session['audiobook'] = (
session['audiobook']
if session['audiobook'] in [option[1] for option in audiobook_options]
else None
)
if len(audiobook_options) > 0:
if session['audiobook'] is not None:
return gr.update(choices=audiobook_options, value=session['audiobook'])
else:
return gr.update(choices=audiobook_options, value=audiobook_options[0][1])
else:
return gr.update(choices=audiobook_options, value=None)
except Exception as e:
error = f'update_gr_audiobook_list(): {e}!'
alert_exception(error, id)
return gr.update()
def change_gr_restore_session(data:DictProxy|None, state:dict, req:gr.Request)->tuple:
try:
nonlocal models
msg = 'Error while loading saved session. Please try to delete your cookies and refresh the page'
if not data.get('id', False):
session = context.set_session(str(uuid.uuid4()))
else:
session = context.set_session(data.get('id'))
if len(active_sessions) == 0 or (data and data.get('status', None) is None):
restore_session_from_data(data, session)
session['status'] = None
if not context_tracker.start_session(session['id']):
error = "Your session is already active.<br>If it's not the case please close your browser and relaunch it."
return gr.update(), gr.update(), gr.update(value=''), update_gr_glassmask(str=error)
else:
active_sessions.add(req.session_hash)
session[req.session_hash] = req.session_hash
session['cancellation_requested'] = False
if isinstance(session['ebook'], str):
if not os.path.exists(session['ebook']):
session['ebook'] = None
if session['voice'] is not None:
if not os.path.exists(session['voice']):
session['voice'] = None
if session['custom_model'] is not None:
if not os.path.exists(session['custom_model_dir']):
session['custom_model'] = None
if session['fine_tuned'] is not None:
if session['tts_engine'] is not None:
models = load_engine_presets(session['tts_engine'])
if session['tts_engine'] in models.keys():
if session['fine_tuned'] not in models.keys():
session['fine_tuned'] = default_fine_tuned
else:
session['tts_engine'] = default_tts_engine
session['fine_tuned'] = default_fine_tuned
if session['audiobook'] is not None:
if not os.path.exists(session['audiobook']):
session['audiobook'] = None
if session['status'] == 'converting':
session['status'] = 'ready'
session['is_gui_process'] = is_gui_process
session['system'] = (f"{platform.system()}-{platform.release()}").lower()
session['session_dir'] = os.path.join(tmp_dir, f"proc-{session['id']}")
session['custom_model_dir'] = os.path.join(models_dir, '__sessions', f"model-{session['id']}")
session['voice_dir'] = os.path.join(voices_dir, '__sessions', f"voice-{session['id']}", session['language'])
os.makedirs(session['custom_model_dir'], exist_ok=True)
os.makedirs(session['voice_dir'], exist_ok=True)
# As now uploaded voice files are in their respective language folder so check if no wav and bark folder are on the voice_dir root from previous versions
#[shutil.move(src, os.path.join(session['voice_dir'], os.path.basename(src))) for src in glob(os.path.join(os.path.dirname(session['voice_dir']), '*.wav')) + ([os.path.join(os.path.dirname(session['voice_dir']), 'bark')] if os.path.isdir(os.path.join(os.path.dirname(session['voice_dir']), 'bark')) and not os.path.exists(os.path.join(session['voice_dir'], 'bark')) else [])]
if is_gui_shared:
msg = f' Note: access limit time: {interface_shared_tmp_expire} days'
session['audiobooks_dir'] = os.path.join(audiobooks_gradio_dir, f"web-{session['id']}")
delete_unused_tmp_dirs(audiobooks_gradio_dir, interface_shared_tmp_expire, session['id'])
else:
msg = f' Note: if no activity is detected after {tmp_expire} days, your session will be cleaned up.'
session['audiobooks_dir'] = os.path.join(audiobooks_host_dir, f"web-{session['id']}")
delete_unused_tmp_dirs(audiobooks_host_dir, tmp_expire, session['id'])
if not os.path.exists(session['audiobooks_dir']):
os.makedirs(session['audiobooks_dir'], exist_ok=True)
previous_hash = state['hash']
new_hash = hash_proxy_dict(MappingProxyType(session))
state['hash'] = new_hash
show_alert({"type": "info", "msg": msg})
return gr.update(value=json.dumps(session, cls=JSONDictProxyEncoder)), gr.update(value=state), gr.update(value=session['id']), gr.update()
except Exception as e:
error = f'change_gr_restore_session(): {e}'
alert_exception(error, None)
return gr.update(), gr.update(), gr.update(), gr.update()
async def update_gr_save_session(id:str, state:dict)->tuple:
try:
if id and id in context.sessions:
session = context.get_session(id)
if session:
previous_hash = state.get("hash")
if session.get("status") == "converting":
try:
if session.get("progress") != len(audiobook_options):
session["progress"] = len(audiobook_options)
new_hash = hash_proxy_dict(MappingProxyType(session))
state["hash"] = new_hash
session_dict = json.dumps(
session, cls=JSONDictProxyEncoder
)
yield (
gr.update(value=session_dict),
gr.update(value=state),
update_gr_audiobook_list(id),
)
else:
yield gr.update(), gr.update(), gr.update()
except NameError:
new_hash = hash_proxy_dict(MappingProxyType(session))
state["hash"] = new_hash
session_dict = json.dumps(
session, cls=JSONDictProxyEncoder
)
yield (
gr.update(value=session_dict),
gr.update(value=state),
gr.update(),
)
else:
new_hash = hash_proxy_dict(MappingProxyType(session))
if previous_hash == new_hash:
yield gr.update(), gr.update(), gr.update()
else:
state["hash"] = new_hash
session_dict = json.dumps(session, cls=JSONDictProxyEncoder)
yield (
gr.update(value=session_dict),
gr.update(value=state),
gr.update(),
)
yield gr.update(), gr.update(), gr.update()
except Exception as e:
error = f'update_gr_save_session(): {e}!'
alert_exception(error, id)
yield gr.update(), gr.update(value=e), gr.update()
def clear_event(id:str)->None:
if id:
session = context.get_session(id)
if session:
if session['event'] is not None:
session['event'] = None
gr_ebook_file.change(
fn=change_convert_btn,
inputs=[gr_ebook_file, gr_ebook_mode, gr_custom_model_file, gr_session],
outputs=[gr_convert_btn]
).then(
fn=change_gr_ebook_file,
inputs=[gr_ebook_file, gr_session],
outputs=[gr_modal]
)
gr_ebook_mode.change(
fn=change_gr_ebook_mode,
inputs=[gr_ebook_mode, gr_session],
outputs=[gr_ebook_file, gr_chapters_preview]
)
gr_chapters_preview.select(
fn=lambda val, id: change_param('chapters_preview', bool(val), id),
inputs=[gr_chapters_preview, gr_session],
outputs=None
)
gr_voice_file.upload(
fn=change_gr_voice_file,
inputs=[gr_voice_file, gr_session],
outputs=[gr_voice_list]
).then(
fn=lambda: gr.update(value=None),
inputs=None,
outputs=[gr_voice_file]
)
gr_voice_list.change(
fn=change_gr_voice_list,
inputs=[gr_voice_list, gr_session],
outputs=[gr_voice_player_hidden, gr_voice_play, gr_voice_del_btn]
)
gr_voice_del_btn.click(
fn=click_gr_voice_del_btn,
inputs=[gr_voice_list, gr_session],
outputs=[gr_confirm_deletion_field_hidden, gr_modal]
)
gr_device.change(
fn=change_gr_device,
inputs=[gr_device, gr_session],
outputs=None
)
gr_language.change(
fn=change_gr_language,
inputs=[gr_language, gr_session],
outputs=[gr_language, gr_tts_engine_list, gr_custom_model_list, gr_fine_tuned_list, gr_voice_list]
)
gr_tts_engine_list.change(
fn=change_gr_tts_engine_list,
inputs=[gr_tts_engine_list, gr_session],
outputs=[gr_tts_rating, gr_tab_xtts_params, gr_tab_bark_params, gr_group_custom_model, gr_fine_tuned_list, gr_custom_model_file, gr_voice_list, gr_custom_model_label]
)
gr_fine_tuned_list.change(
fn=change_gr_fine_tuned_list,
inputs=[gr_fine_tuned_list, gr_session],
outputs=[gr_group_custom_model, gr_voice_list]
)
gr_custom_model_file.upload(
fn=change_gr_custom_model_file,
inputs=[gr_custom_model_file, gr_tts_engine_list, gr_session],
outputs=[gr_custom_model_file, gr_custom_model_list, gr_voice_list],
show_progress_on=[gr_custom_model_list]
)
gr_custom_model_list.change(
fn=change_gr_custom_model_list,
inputs=[gr_custom_model_list, gr_session],
outputs=[gr_fine_tuned_list, gr_custom_model_del_btn, gr_voice_list]
)
gr_custom_model_del_btn.click(
fn=click_gr_custom_model_del_btn,
inputs=[gr_custom_model_list, gr_session],
outputs=[gr_confirm_deletion_field_hidden, gr_modal]
)
gr_output_format_list.change(
fn=change_gr_output_format_list,
inputs=[gr_output_format_list, gr_session],
outputs=None
)
gr_output_channel_list.change(
fn=change_gr_output_channel_list,
inputs=[gr_output_channel_list, gr_session],
outputs=None
)
gr_output_split.select(
fn=change_gr_output_split,
inputs=[gr_output_split, gr_session],
outputs=[gr_row_output_split_hours]
)
gr_output_split_hours.change(
fn=lambda val, id: change_param('output_split_hours', str(val), id),
inputs=[gr_output_split_hours, gr_session],
outputs=None
)
gr_progress.change(
fn=None,
inputs=[gr_progress],
js=r'''
(filename)=>{
const gr_root = (window.gradioApp && window.gradioApp()) || document;
const gr_ebook_file = gr_root.querySelector("#gr_ebook_file");
if(!gr_ebook_file){
return;
}
function normalizeForGradio(name){
return name
.normalize("NFC")
// Remove chars not supported by OS paths
.replace(/[<>:"/\\|?*\x00-\x1F]/g, "")
// Remove Gradio-sanitized odd punctuation (including quotes)
.replace(/[!(){}\[\]']/g, "")
// Collapse multiple dots/spaces before extension
.replace(/\s+\./g, ".")
// Strip trailing spaces/dots (Windows forbids)
.replace(/[. ]+$/, "")
// Remove Arabic tatweel/harakat
.replace(/[\u0640\u0651\u064B-\u065F]/g, "")
.trim();
}
const rows = gr_ebook_file.querySelectorAll("table.file-preview tr.file");
rows.forEach((row, idx) => {
const filenameCell = row.querySelector("td.filename");
if (filenameCell) {
const rowName = normalizeForGradio(filenameCell.getAttribute("aria-label"));
filename = filename.split("/")[0].trim();
if (rowName === filename) {
row.style.display = "none";
}
}
});
}
'''
)
gr_playback_time.change(
fn=change_gr_playback_time,
inputs=[gr_playback_time, gr_session],
js='''
(time)=>{
try{
window.session_storage.playback_time = Number(time);
}catch(e){
console.warn("gr_playback_time.change error: "+e);
}
}
'''
)
gr_audiobook_download_btn.click(
fn=toggle_audiobook_files,
inputs=[gr_audiobook_list, gr_audiobook_files_toggled],
outputs=[gr_audiobook_files, gr_audiobook_files_toggled],
show_progress="minimal",
)
gr_audiobook_list.change(
fn=change_gr_audiobook_list,
inputs=[gr_audiobook_list, gr_session],
outputs=[gr_group_audiobook_list]
).then(
fn=update_gr_audiobook_player,
inputs=[gr_session],
outputs=[gr_playback_time, gr_audiobook_player, gr_audiobook_vtt]
).then(
fn=None,
inputs=None,
js='()=>{window.load_vtt();}'
)
gr_audiobook_del_btn.click(
fn=click_gr_audiobook_del_btn,
inputs=[gr_audiobook_list, gr_session],
outputs=[gr_confirm_deletion_field_hidden, gr_modal]
)
########### XTTSv2 Params
gr_tab_xtts_params.select(
fn=None,
inputs=None,
outputs=None,
js='''
() => {
if (!window._xtts_sliders_initialized) {
const checkXttsExist = setInterval(() => {
const slider = document.querySelector("#gr_xtts_speed input[type=range]");
if(slider){
clearInterval(checkXttsExist);
window._xtts_sliders_initialized = true;
init_xtts_sliders();
}
}, 500);
}
}
'''
)
gr_xtts_temperature.change(
fn=lambda val, id: change_param('xtts_temperature', float(val), id),
inputs=[gr_xtts_temperature, gr_session],
outputs=None
)
gr_xtts_length_penalty.change(
fn=lambda val, id, val2: change_param('xtts_length_penalty', int(val), id, int(val2)),
inputs=[gr_xtts_length_penalty, gr_session, gr_xtts_num_beams],
outputs=None,
)
gr_xtts_num_beams.change(
fn=lambda val, id, val2: change_param('xtts_num_beams', int(val), id, int(val2)),
inputs=[gr_xtts_num_beams, gr_session, gr_xtts_length_penalty],
outputs=None,
)
gr_xtts_repetition_penalty.change(
fn=lambda val, id: change_param('xtts_repetition_penalty', float(val), id),
inputs=[gr_xtts_repetition_penalty, gr_session],
outputs=None
)
gr_xtts_top_k.change(
fn=lambda val, id: change_param('xtts_top_k', int(val), id),
inputs=[gr_xtts_top_k, gr_session],
outputs=None
)
gr_xtts_top_p.change(
fn=lambda val, id: change_param('xtts_top_p', float(val), id),
inputs=[gr_xtts_top_p, gr_session],
outputs=None
)
gr_xtts_speed.change(
fn=lambda val, id: change_param('xtts_speed', float(val), id),
inputs=[gr_xtts_speed, gr_session],
outputs=None
)
gr_xtts_enable_text_splitting.select(
fn=lambda val, id: change_param('xtts_enable_text_splitting', bool(val), id),
inputs=[gr_xtts_enable_text_splitting, gr_session],
outputs=None
)
########### BARK Params
gr_tab_bark_params.select(
fn=None,
inputs=None,
outputs=None,
js='''
() => {
if (!window._bark_sliders_initialized) {
const checkBarkExist = setInterval(() => {
const slider = document.querySelector("#gr_bark_waveform_temp input[type=range]");
if(slider){
clearInterval(checkBarkExist);
window._bark_sliders_initialized = true;
init_bark_sliders();
}
}, 500);
}
}
'''
)
gr_bark_text_temp.change(
fn=lambda val, id: change_param('bark_text_temp', float(val), id),
inputs=[gr_bark_text_temp, gr_session],
outputs=None
)
gr_bark_waveform_temp.change(
fn=lambda val, id: change_param('bark_waveform_temp', float(val), id),
inputs=[gr_bark_waveform_temp, gr_session],
outputs=None
)
############ Timer to save session to localStorage
gr_timer = gr.Timer(9, active=False)
gr_timer.tick(
fn=update_gr_save_session,
inputs=[gr_session, gr_state_update],
outputs=[gr_save_session, gr_state_update, gr_audiobook_list]
).then(
fn=clear_event,
inputs=[gr_session],
outputs=None
)
gr_convert_btn.click(
fn=change_convert_btn,
inputs=None,
outputs=[gr_convert_btn]
).then(
fn=disable_components,
inputs=None,
outputs=[gr_ebook_mode, gr_chapters_preview, gr_language, gr_voice_file, gr_voice_list, gr_device, gr_tts_engine_list, gr_fine_tuned_list, gr_custom_model_file, gr_custom_model_list, gr_output_format_list, gr_output_channel_list]
).then(
fn=submit_convert_btn,
inputs=[
gr_session, gr_device, gr_ebook_file, gr_chapters_preview, gr_tts_engine_list, gr_language, gr_voice_list,
gr_custom_model_list, gr_fine_tuned_list, gr_output_format_list, gr_output_channel_list,
gr_xtts_temperature, gr_xtts_length_penalty, gr_xtts_num_beams, gr_xtts_repetition_penalty, gr_xtts_top_k, gr_xtts_top_p, gr_xtts_speed, gr_xtts_enable_text_splitting,
gr_bark_text_temp, gr_bark_waveform_temp, gr_output_split, gr_output_split_hours
],
outputs=[gr_progress, gr_modal]
).then(
fn=enable_components,
inputs=[gr_session],
outputs=[gr_ebook_mode, gr_chapters_preview, gr_language, gr_voice_file, gr_voice_list, gr_device, gr_tts_engine_list, gr_fine_tuned_list, gr_custom_model_file, gr_custom_model_list, gr_output_format_list, gr_output_channel_list]
).then(
fn=refresh_interface,
inputs=[gr_session],
outputs=[gr_convert_btn, gr_ebook_file, gr_device, gr_audiobook_list, gr_audiobook_player, gr_modal, gr_voice_list, gr_progress]
)
gr_save_session.change(
fn=None,
inputs=[gr_save_session],
js='''
(data)=>{
try{
if(data){
localStorage.clear();
data.playback_time = Number(window.session_storage.playback_time);
data.playback_volume = parseFloat(window.session_storage.playback_volume);
localStorage.setItem("data", JSON.stringify(data));
}
}catch(e){
console.warn("gr_save_session.change error: "+e);
}
}
'''
)
gr_restore_session.change(
fn=change_gr_restore_session,
inputs=[gr_restore_session, gr_state_update],
outputs=[gr_save_session, gr_state_update, gr_session, gr_glassmask]
).then(
fn=restore_interface,
inputs=[gr_session],
outputs=[
gr_ebook_file, gr_ebook_mode, gr_chapters_preview, gr_device, gr_language, gr_voice_list,
gr_tts_engine_list, gr_custom_model_list, gr_fine_tuned_list, gr_output_format_list, gr_output_channel_list,
gr_output_split, gr_output_split_hours, gr_row_output_split_hours, gr_audiobook_list
]
).then(
fn=restore_audiobook_player,
inputs=[gr_audiobook_list],
outputs=[
gr_group_audiobook_list, gr_audiobook_player, gr_timer
]
).then(
fn=lambda session: update_gr_glassmask(attr=['gr-glass-mask', 'hide']) if session else gr.update(),
inputs=[gr_session],
outputs=[gr_glassmask]
).then(
fn=None,
inputs=None,
js='()=>{init_interface();}'
)
gr_confirm_deletion_yes_btn.click(
fn=confirm_deletion,
inputs=[gr_voice_list, gr_custom_model_list, gr_audiobook_list, gr_session, gr_confirm_deletion_field_hidden],
outputs=[gr_custom_model_list, gr_audiobook_list, gr_modal, gr_voice_list]
)
gr_confirm_deletion_no_btn.click(
fn=confirm_deletion,
inputs=[gr_voice_list, gr_custom_model_list, gr_audiobook_list, gr_session],
outputs=[gr_custom_model_list, gr_audiobook_list, gr_modal, gr_voice_list]
)
gr_confirm_blocks_yes_btn.click(
fn=lambda session: confirm_blocks("yes", session),
inputs=[gr_session],
outputs=[gr_modal]
).then(
fn=submit_confirmed_blocks,
inputs=[gr_session],
outputs=[gr_progress, gr_modal]
).then(
fn=enable_components,
inputs=[gr_session],
outputs=[gr_ebook_mode, gr_chapters_preview, gr_language, gr_voice_file, gr_voice_list, gr_device, gr_tts_engine_list, gr_fine_tuned_list, gr_custom_model_file, gr_custom_model_list]
).then(
fn=refresh_interface,
inputs=[gr_session],
outputs=[gr_convert_btn, gr_ebook_file, gr_device, gr_audiobook_list, gr_audiobook_player, gr_modal, gr_voice_list, gr_progress]
)
gr_confirm_blocks_no_btn.click(
fn=lambda session: confirm_blocks("no", session),
inputs=[gr_session],
outputs=[gr_modal]
).then(
fn=change_convert_btn,
inputs=[gr_ebook_file, gr_ebook_mode, gr_custom_model_file, gr_session],
outputs=[gr_convert_btn]
).then(
fn=enable_components,
inputs=[gr_session],
outputs=[gr_ebook_mode, gr_chapters_preview, gr_language, gr_voice_file, gr_voice_list, gr_device, gr_tts_engine_list, gr_fine_tuned_list, gr_custom_model_file, gr_custom_model_list]
)
############
app.load(
fn=None,
js=r'''
()=>{
try{
let gr_root = (window.gradioApp && window.gradioApp()) || document;
let gr_checkboxes;
let gr_radios;
let gr_voice_player_hidden;
let gr_audiobook_vtt;
let gr_audiobook_sentence;
let gr_audiobook_player;
let gr_playback_time;
let gr_progress;
let gr_voice_play;
let tabs_opened = false;
let init_elements_timeout;
let init_audiobook_player_timeout;
let audio_filter = "";
let cues = [];
if(typeof window.onElementAvailable !== "function"){
window.onElementAvailable = (selector, callback, { root = (window.gradioApp && window.gradioApp()) || document, once = false } = {})=> {
const seen = new WeakSet();
const fireFor = (context) => {
context.querySelectorAll(selector).forEach((el) => {
if (seen.has(el)) return;
const success = callback(el);
if (success !== false) {
// Mark as seen only if callback succeeded
seen.add(el);
if (once) return;
} else {
// Retry check later (in case conditions werent met yet)
setTimeout(() => fireFor(root), 300);
}
});
};
fireFor(root);
const observer = new MutationObserver((mutations) => {
for (const m of mutations) {
for (const n of m.addedNodes) {
if (n.nodeType !== 1) continue;
if (n.matches?.(selector)) {
if (!seen.has(n)) {
const success = callback(n);
if (success !== false) {
seen.add(n);
if (once) {
observer.disconnect();
return;
}
} else {
setTimeout(() => fireFor(root), 300);
}
}
} else {
fireFor(n);
}
}
}
});
observer.observe(root, { childList: true, subtree: true });
return () => observer.disconnect();
}
}
if(typeof window.init_interface !== "function"){
window.init_interface = ()=>{
try {
gr_root = (window.gradioApp && window.gradioApp()) || document;
gr_progress = gr_root.querySelector("#gr_progress");
if(!gr_root || !gr_progress){
clearTimeout(init_elements_timeout);
console.warn("Components not ready... retrying");
init_elements_timeout = setTimeout(init_interface, 1000);
return;
}
// Function to apply theme borders
function applyThemeBorders(){
const url = new URL(window.location);
const theme = url.searchParams.get("__theme");
let elColor = "#666666";
if(theme == "dark"){
elColor = "#fff";
}else if(!theme){
const osTheme = window.matchMedia?.("(prefers-color-scheme: dark)").matches;
if(osTheme){
elColor = "#fff";
}
}
gr_root.querySelectorAll("input[type='checkbox'], input[type='radio']")
.forEach(cb => cb.style.border = "1px solid " + elColor);
}
// Run once on init
applyThemeBorders();
// Re-run when DOM changes (tabs, redraws, etc.)
new MutationObserver(applyThemeBorders).observe(gr_root, {
childList: true,
subtree: true
});
// Keep your progress observer too
new MutationObserver(tab_progress).observe(gr_progress, {
attributes: true,
childList: true,
subtree: true,
characterData: true
});
gr_progress.addEventListener("change", tab_progress);
}catch(e){
console.warn("init_interface error:", e);
}
};
}
if(typeof(window.init_xtts_sliders) !== "function"){
window.init_xtts_sliders = ()=>{
try{
const gr_xtts_temperature = gr_root.querySelector("#gr_xtts_temperature input[type=number]");
const gr_xtts_repetition_penalty = gr_root.querySelector("#gr_xtts_repetition_penalty input[type=number]");
const gr_xtts_top_k = gr_root.querySelector("#gr_xtts_top_k input[type=number]");
const gr_xtts_top_p = gr_root.querySelector("#gr_xtts_top_p input[type=number]");
const gr_xtts_speed = gr_root.querySelector("#gr_xtts_speed input[type=number]");
const sliders = [
gr_xtts_temperature,
gr_xtts_repetition_penalty,
gr_xtts_top_k,
gr_xtts_top_p,
gr_xtts_speed
];
sliders.forEach(slider => {
if(!slider) return;
const key = slider.closest("div[id]").id.replace(/^gr_/, "");
const saved = window.session_storage[key];
slider.value = (slider === gr_xtts_top_k) ? parseInt(saved) : parseFloat(saved);
slider.dispatchEvent(new Event("input", { bubbles: true }));
});
}catch(e){
console.warn("init_xtts_sliders error:", e);
}
};
}
if(typeof(window.init_bark_sliders) !== "function"){
window.init_bark_sliders = ()=>{
try{
const gr_bark_text_temp_slider = gr_root.querySelector("#gr_bark_text_temp input[type=number]");
const gr_bark_waveform_temp_slider = gr_root.querySelector("#gr_bark_waveform_temp input[type=number]");
const sliders = [
gr_bark_text_temp_slider,
gr_bark_waveform_temp_slider
];
sliders.forEach(slider => {
if(!slider) return;
const key = slider.closest("div[id]").id.replace(/^gr_/, "");
const saved = window.session_storage[key];
slider.value = parseFloat(saved);
slider.dispatchEvent(new Event("input", { bubbles: true }));
});
}catch(e){
console.warn("init_bark_sliders error:", e);
}
};
}
if(typeof window.init_voice_player_hidden !== "function"){
window.init_voice_player_hidden = ()=>{
try{
const gr_voice_player_hidden = gr_root.querySelector("#gr_voice_player_hidden audio");
const gr_voice_play = gr_root.querySelector("#gr_voice_play");
if(gr_voice_player_hidden && gr_voice_play){
if(gr_voice_play.dataset.bound === "true") return;
gr_voice_play.dataset.bound = "true";
gr_voice_player_hidden.addEventListener("loadeddata", ()=>{
gr_voice_play.textContent = "";
});
gr_voice_play.addEventListener("click", ()=>{
if(gr_voice_player_hidden.paused){
gr_voice_player_hidden.play().then(()=>{
gr_voice_play.textContent = "";
}).catch(err => console.warn("Play failed:", err));
}else{
gr_voice_player_hidden.pause();
gr_voice_play.textContent = "";
}
});
gr_voice_player_hidden.addEventListener("pause", ()=>{
gr_voice_play.textContent = "";
});
gr_voice_player_hidden.addEventListener("ended", ()=>{
gr_voice_play.textContent = "";
});
gr_voice_player_hidden.addEventListener("play", ()=>{
const v = window.session_storage?.playback_volume ?? 1;
gr_voice_player_hidden.volume = v;
});
return true;
}else{
console.warn("Voice player not found yet, retrying...");
setTimeout(window.init_voice_player_hidden, 500);
}
}catch(e){
console.warn("init_voice_player_hidden error:", e);
}
return false;
};
}
if(typeof(window.init_audiobook_player) !== "function"){
window.init_audiobook_player = ()=>{
try{
if(gr_root){
gr_audiobook_player = gr_root.querySelector("#gr_audiobook_player audio");
gr_audiobook_sentence = gr_root.querySelector("#gr_audiobook_sentence textarea");
gr_playback_time = gr_root.querySelector("#gr_playback_time input");
let lastCue = null;
let fade_timeout = null;
let last_time = 0;
if(gr_audiobook_player && gr_audiobook_sentence && gr_playback_time){
function trackPlayback(){
try {
window.session_storage.playback_time = parseFloat(gr_audiobook_player.currentTime);
const cue = findCue(window.session_storage.playback_time);
if(cue && cue !== lastCue){
if(fade_timeout){
gr_audiobook_sentence.style.opacity = "1";
}else{
gr_audiobook_sentence.style.opacity = "0";
}
gr_audiobook_sentence.style.transition = "none";
gr_audiobook_sentence.value = cue.text;
clearTimeout(fade_timeout);
fade_timeout = setTimeout(() => {
gr_audiobook_sentence.style.transition = "opacity 0.15s ease-in";
gr_audiobook_sentence.style.opacity = "1";
fade_timeout = null;
}, 33);
lastCue = cue;
}else if(!cue && lastCue !== null){
lastCue = null;
}
const now = performance.now();
if(now - last_time > 1000){
gr_playback_time.value = String(window.session_storage.playback_time);
gr_playback_time.dispatchEvent(new Event("input", {bubbles: true}));
last_time = now;
}
}catch(e){
console.warn("gr_audiobook_player tracking error:", e);
}
if(!gr_audiobook_player.ended){
requestAnimationFrame(trackPlayback);
}
}
gr_audiobook_player.addEventListener("loadeddata", ()=>{
gr_audiobook_player.style.transition = "filter 1s ease";
gr_audiobook_player.style.filter = audio_filter;
gr_audiobook_player.currentTime = parseFloat(window.session_storage.playback_time);
gr_audiobook_player.volume = window.session_storage.playback_volume;
});
gr_audiobook_player.addEventListener("play", ()=>{
requestAnimationFrame(trackPlayback);
});
gr_audiobook_player.addEventListener("seeked", ()=>{
window.session_storage.playback_time = gr_audiobook_player.currentTime;
requestAnimationFrame(trackPlayback);
});
gr_audiobook_player.addEventListener("ended", ()=>{
gr_audiobook_sentence.value = "...";
window.session_storage.playback_time = 0;
lastCue = null;
});
gr_audiobook_player.addEventListener("volumechange", ()=>{
window.session_storage.playback_volume = gr_audiobook_player.volume;
gr_voice_player_hidden = gr_root.querySelector("#gr_voice_player_hidden audio");
if(gr_voice_player_hidden){
gr_voice_player_hidden.volume = gr_audiobook_player.volume;
gr_voice_player_hidden.dispatchEvent(new Event("volumechange", { bubbles: true }));
}
});
const themURL = new URL(window.location);
const theme = themURL.searchParams.get("__theme");
let osTheme;
if(theme){
if(theme == "dark"){
audio_filter = "invert(1) hue-rotate(180deg)";
}
}else{
osTheme = window.matchMedia?.("(prefers-color-scheme: dark)").matches;
if(osTheme){
audio_filter = "invert(1) hue-rotate(180deg)";
}
}
gr_audiobook_player.style.transition = "filter 1s ease";
gr_audiobook_player.style.filter = audio_filter;
gr_audiobook_player.volume = window.session_storage.playback_volume;
return true;
}
}
}catch(e){
console.warn("init_audiobook_player error:", e);
}
return false;
};
}
if(typeof(window.tab_progress) !== "function"){
window.tab_progress = ()=>{
try{
const val = gr_progress?.value || gr_progress?.textContent || "";
const valArray = splitAtLastDash(val);
if(valArray[1]){
const title = valArray[0].trim().split(/ (.*)/)[1].trim();
const percentage = valArray[1].trim();
const titleShort = title.length >= 20 ? title.slice(0, 20).trimEnd() + "" : title;
document.title = titleShort + ": " + percentage;
}else{
document.title = "Ebook2Audiobook";
}
}catch(e){
console.warn("tab_progress error:", e);
}
};
}
if(typeof(splitAtLastDash) !== "function"){
function splitAtLastDash(s){
const idx = s.lastIndexOf("-");
if(idx === -1){
return [s];
}
return [s.slice(0, idx).trim(), s.slice(idx + 1).trim()];
}
}
if(typeof(window.load_vtt) !== "function"){
window.load_vtt = ()=>{
try{
gr_audiobook_vtt = gr_root.querySelector("#gr_audiobook_vtt textarea");
gr_audiobook_sentence = gr_root.querySelector("#gr_audiobook_sentence textarea");
if(gr_audiobook_sentence){
gr_audiobook_sentence.style.fontSize = "14px";
gr_audiobook_sentence.style.fontWeight = "bold";
gr_audiobook_sentence.style.width = "100%";
gr_audiobook_sentence.style.height = "auto";
gr_audiobook_sentence.style.textAlign = "center";
gr_audiobook_sentence.style.margin = "0";
gr_audiobook_sentence.style.padding = "7px 0 7px 0";
gr_audiobook_sentence.style.lineHeight = "14px";
const txt = gr_audiobook_vtt.value;
if(txt == ""){
gr_audiobook_sentence.value = "...";
}else{
parseVTT(txt);
}
}
}catch(e){
console.warn("load_vtt error:", e);
}
};
}
if(typeof(parseVTT) !== "function"){
window.parseVTT = (vtt)=>{
function pushCue(){
if(start !== null && end !== null && textBuffer.length){
cues.push({ start, end, text: textBuffer.join("\n") });
}
start = end = null;
textBuffer.length = 0;
}
const lines = vtt.split(/\r?\n/);
const timePattern = /(\d{2}:)?\d{2}:\d{2}\.\d{3}/;
let start = null, end = null;
cues = [];
textBuffer = [];
for(let i = 0, len = lines.length; i < len; i++){
const line = lines[i];
if(!line.trim()){ pushCue(); continue; }
if(line.includes("-->")){
const [s, e] = line.split("-->").map(l => l.trim().split(" ")[0]);
if(timePattern.test(s) && timePattern.test(e)){
start = toSeconds(s);
end = toSeconds(e);
}
}else if(!timePattern.test(line)){
textBuffer.push(line);
}
}
pushCue();
}
}
if(typeof(toSeconds) !== "function"){
function toSeconds(ts){
const parts = ts.split(":");
if(parts.length === 3){
return parseInt(parts[0], 10) * 3600 +
parseInt(parts[1], 10) * 60 +
parseFloat(parts[2]);
}
return parseInt(parts[0], 10) * 60 + parseFloat(parts[1]);
}
}
if(typeof(findCue) !== "function"){
function findCue(time){
let lo = 0, hi = cues.length - 1;
while(lo <= hi){
const mid = (lo + hi) >> 1;
const cue = cues[mid];
if(time < cue.start){
hi = mid - 1;
}else if(time >= cue.end){
lo = mid + 1;
}else{
return cue;
}
}
return null;
}
}
if(typeof(splitAtLastDash) !== "function"){
function splitAtLastDash(s){
const idx = s.lastIndexOf("-");
if(idx === -1){
return [s];
}
return [s.slice(0, idx).trim(), s.slice(idx + 1).trim()];
}
}
if(typeof(show_glassmask) !== "function"){
function show_glassmask(msg){
let glassmask = document.querySelector("#gr_glassmask");
if(!glassmask){
glassmask = document.createElement("div");
glassmask.id = "gr_glassmask";
document.body.appendChild(glassmask);
}
glassmask.className = "gr-glass-mask";
glassmask.innerHTML = `${msg}`;
}
}
if(typeof(create_uuid) !== "function"){
function create_uuid(){
try{
return crypto.randomUUID();
}catch(e){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c =>{
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}
}
//////////////////////
const bc = new BroadcastChannel("E2A-channel");
const tab_id = create_uuid();
bc.onmessage = (event)=>{
try{
const msg = event.data;
if(!msg || msg.senderId === tab_id){
return;
}
switch (msg.type){
case "check-existing":
bc.postMessage({ type: "already-open", senderId: tab_id });
break;
case "already-open":
tabs_opened = true;
break;
case "new-tab-opened":
show_glassmask(msg.text);
break;
}
}catch(e){
console.warn("bc.onmessage error:", e);
}
};
window.addEventListener("beforeunload", ()=>{
try{
const newStorage = JSON.parse(localStorage.getItem("data") || "{}");
if(newStorage.tab_id == window.tab_id || !newStorage.tab_id){
delete newStorage.tab_id;
delete newStorage.status;
newStorage.playback_time = Number(window.session_storage.playback_time);
newStorage.playback_volume = parseFloat(window.session_storage.playback_volume);
localStorage.setItem("data", JSON.stringify(newStorage));
}
}catch(e){
console.warn("Error updating status on unload:", e);
}
});
const currentStorage = localStorage.getItem("data");
if(currentStorage){
window.session_storage = JSON.parse(currentStorage);
window.session_storage.tab_id = tab_id;
if(window.session_storage.playback_volume === 0){
window.session_storage.playback_volume = 1.0;
}
}else{
window.session_storage = {};
window.session_storage.playback_time = 0;
window.session_storage.playback_volume = 1.0;
}
window.onElementAvailable("#gr_voice_player_hidden audio", (el)=>{
window.init_voice_player_hidden();
}, {once: false});
window.onElementAvailable("#gr_audiobook_player audio", (el)=>{
window.init_audiobook_player();
}, {once: false});
try{
bc.postMessage({ type: "check-existing", senderId: tab_id });
setTimeout(()=>{
if(tabs_opened){
bc.postMessage({
type: "new-tab-opened",
text: "Session expired.<br/>You can close this window",
senderId: tab_id
});
}
}, 250);
}catch(e){
console.warn("bc.postMessage error:", e);
}
return window.session_storage;
}catch(e){
console.warn("gr_raed_data js error:", e);
}
return null;
}
''',
outputs=[gr_restore_session],
)
app.unload(cleanup_session)
all_ips = get_all_ip_addresses()
msg = f'IPs available for connection:\n{all_ips}\nNote: 0.0.0.0 is not the IP to connect. Instead use an IP above to connect and port {interface_port}'
show_alert({"type": "info", "msg": msg})
os.environ['no_proxy'] = ' ,'.join(all_ips)
return app
except Exception as e:
error = f'An unexpected error occurred: {e}'
alert_exception(error, None)
return None