mirror of
https://github.com/DrewThomasson/ebook2audiobook.git
synced 2026-01-09 13:58:14 -05:00
2681 lines
157 KiB
Python
2681 lines
157 KiB
Python
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> <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'
|
||
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:
|
||
session['voice'] = voice_options[0][1]
|
||
else:
|
||
if voice_options and voice_options[0][1] is not None:
|
||
new_voice_path = models[session['fine_tuned']]['voice']
|
||
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:
|
||
session['voice'] = voice_options[0][1]
|
||
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)
|
||
)
|
||
return 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)
|
||
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()
|
||
|
||
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']
|
||
visible_bark = False
|
||
visible_xtts = False
|
||
if session['tts_engine'] == TTS_ENGINES['XTTSv2']:
|
||
visible_custom_model = True if session['fine_tuned'] == 'internal' else False
|
||
visible_xtts = visible_gr_tab_xtts_params
|
||
return (
|
||
gr.update(value=show_rating(session['tts_engine'])),
|
||
gr.update(visible=visible_xtts),
|
||
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'])})"),
|
||
gr.update(value=f"My {session['tts_engine']} Custom Models")
|
||
)
|
||
else:
|
||
if session['tts_engine'] == TTS_ENGINES['BARK']:
|
||
visible_bark = visible_gr_tab_bark_params
|
||
return (
|
||
gr.update(value=show_rating(session['tts_engine'])),
|
||
gr.update(visible=False),
|
||
gr.update(visible=visible_bark),
|
||
gr.update(visible=False),
|
||
update_gr_fine_tuned_list(id),
|
||
gr.update(label=f"*Upload Custom Model not available for {session['tts_engine']}"),
|
||
gr.update(value='')
|
||
)
|
||
outputs = tuple([gr.update(interactive=False) for _ in range(7)])
|
||
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]
|
||
).then(
|
||
fn=update_gr_voice_list,
|
||
inputs=[gr_session],
|
||
outputs=[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_custom_model_label]
|
||
).then(
|
||
fn=update_gr_voice_list,
|
||
inputs=[gr_session],
|
||
outputs=[gr_voice_list]
|
||
)
|
||
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],
|
||
show_progress_on=[gr_custom_model_list]
|
||
).then(
|
||
fn=update_gr_voice_list,
|
||
inputs=[gr_session],
|
||
outputs=[gr_voice_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 weren’t 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 |