AICoverGen / src /webui.py
R-Kentaren's picture
Upload src/webui.py with huggingface_hub
e314ba6 verified
Raw
History Blame Contribute Delete
39.5 kB
import json
import os
import shutil
import urllib.request
import zipfile
from argparse import ArgumentParser
import gradio as gr
import logging
def configure_logging_libs(debug=False):
modules = [
"numba",
"httpx",
"markdown_it",
"fairseq",
"faiss",
]
try:
for module in modules:
logging.getLogger(module).setLevel(logging.WARNING)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = "3" if not debug else "1"
except Exception as error:
pass
configure_logging_libs()
from main import song_cover_pipeline, yt_download
from main import get_history_manager, get_preset_manager, get_rvc_cache, _HAS_MODULES
from ui.config import (
BASE_DIR, IS_ZERO_GPU, rvc_models_dir, AUDIO_FOLDERS_DIR,
AUDIO_EXTENSIONS, SUPPORTED_AUDIO_FORMATS, ROOT_FOLDER_LABEL,
)
from ui.handlers import (
get_current_models, update_models_list, load_public_models,
extract_zip, download_online_model, upload_local_model,
filter_models, pub_dl_autofill, show_hop_slider,
get_audio_files_from_folder, folder_name_to_path, get_audio_folders,
refresh_audio_folders, select_audio_folder, set_selected_audio,
sync_file_to_path, prepare_song_input,
create_audio_folder, upload_to_audio_folder,
hide_audio_folder_selector, show_audio_folder_selector, show_folder_stats,
preset_load_selected, preset_refresh_list, preset_save_current,
preset_delete_selected, preset_reset_builtin, preset_describe,
history_refresh, history_play_selected, history_clear, history_get_stats,
cache_stats_refresh, cache_clear, show_audio_info, show_disk_usage,
set_globals as set_handler_globals,
)
# MAIN APPLICATION
# ============================================
def build_app(args):
"""Construct the Gradio Blocks application. Returns the app object (not yet launched)."""
voice_models = get_current_models(rvc_models_dir)
with open(os.path.join(rvc_models_dir, 'public_models.json'), encoding='utf8') as infile:
public_models = json.load(infile)
# Set globals so handler functions in ui.handlers can access them
set_handler_globals(public_models, voice_models)
with gr.Blocks(title='AICoverGen WebUI', fill_width=True, fill_height=False) as app:
# No top-level markdown headers — keep it clean.
# ============================================
# GENERATE TAB
# ============================================
with gr.Tab("Generate"):
# --- Voice Model & Pitch (no accordion, always visible) ---
with gr.Row():
_default_model = 'Silver' if 'Silver' in voice_models else (voice_models[0] if voice_models else None)
rvc_model = gr.Dropdown(voice_models, value=_default_model, label='Voice Models', scale=3)
ref_btn = gr.Button('Refresh Models', variant='primary', scale=1)
with gr.Row():
pitch = gr.Slider(-3, 3, value=0, step=1, label='Pitch Change (Vocals ONLY)')
pitch_all = gr.Slider(-12, 12, value=0, step=1, label='Overall Pitch Change')
# --- Input source ---
with gr.Accordion('Input Source', open=True):
# Hidden textbox that reliably carries the song path to the pipeline.
# gr.File doesn't pass arbitrary filesystem paths correctly when set
# programmatically (e.g. from the audio folder selector), so we use
# this textbox as the actual input to song_cover_pipeline.
song_input_path = gr.Textbox(visible=False, value="")
with gr.Row():
with gr.Column(visible=True) as file_upload_col:
audio_file_types = [ext for ext in AUDIO_EXTENSIONS if ext.startswith('.')]
local_file = gr.File(label='Audio file', interactive=True, type="filepath", file_types=audio_file_types, height=150)
audio_info_box = gr.Textbox(label="File info", interactive=False, lines=5, max_lines=6)
# Sync the uploaded file path into the hidden textbox
local_file.change(show_audio_info, inputs=[local_file], outputs=[audio_info_box])
local_file.change(sync_file_to_path, inputs=[local_file], outputs=[song_input_path])
if not IS_ZERO_GPU:
with gr.Row():
url_media_gui = gr.Textbox(value="", label="Or enter YouTube URL", placeholder="https://www.youtube.com/watch?v=...", lines=1, scale=3)
url_button_gui = gr.Button("Download from URL", variant="secondary", scale=1)
url_button_gui.click(yt_download, [url_media_gui], [local_file])
show_folder_selector_btn = gr.Button('Select from Audio Folders', variant='secondary')
with gr.Column(visible=False) as audio_folder_col:
with gr.Row():
_init_folders = get_audio_folders()
_init_value = _init_folders[0] if _init_folders else None
folder_dropdown = gr.Dropdown(
choices=_init_folders,
value=_init_value,
label='Select Audio Folder',
interactive=True,
scale=3,
)
refresh_folders_btn = gr.Button('Refresh', variant='secondary', scale=1)
audio_files_list = gr.Dropdown([], label='Select Audio File', interactive=True, multiselect=False)
folder_status = gr.Textbox(label='Status', interactive=False, value="Select a folder to see available audio files")
with gr.Row():
select_audio_btn = gr.Button('Select Audio File', variant='primary')
back_to_upload_btn = gr.Button('Back to File Upload', variant='secondary')
refresh_folders_btn.click(refresh_audio_folders, outputs=folder_dropdown)
folder_dropdown.change(select_audio_folder, inputs=folder_dropdown, outputs=[audio_files_list, folder_status])
# set_selected_audio returns TWO values: gr.update for local_file, path string for song_input_path
select_audio_btn.click(
set_selected_audio,
inputs=[audio_files_list, folder_dropdown],
outputs=[local_file, song_input_path],
).then(
show_audio_info,
inputs=[song_input_path],
outputs=[audio_info_box],
)
show_folder_selector_btn.click(
hide_audio_folder_selector,
outputs=[file_upload_col, audio_folder_col],
).then(
select_audio_folder,
inputs=folder_dropdown,
outputs=[audio_files_list, folder_status],
)
back_to_upload_btn.click(show_audio_folder_selector, outputs=[file_upload_col, audio_folder_col])
# --- All AI options in one accordion ---
with gr.Accordion('AI Options', open=False):
# 1. Voice conversion options
with gr.Accordion('Voice conversion options', open=False):
with gr.Row():
index_rate = gr.Slider(0, 1, value=0.5, label='Index Rate')
filter_radius = gr.Slider(0, 7, value=3, step=1, label='Filter radius')
rms_mix_rate = gr.Slider(0, 1, value=0.25, label='RMS mix rate')
protect = gr.Slider(0, 0.5, value=0.33, label='Protect rate')
with gr.Column():
f0_method = gr.Dropdown(['rmvpe+', 'rmvpe', 'mangio-crepe'], value='rmvpe+', label='Pitch detection algorithm')
crepe_hop_length = gr.Slider(32, 320, value=128, step=1, visible=False, label='Crepe hop length')
f0_method.change(show_hop_slider, inputs=f0_method, outputs=crepe_hop_length)
with gr.Row():
steps = gr.Slider(minimum=1, maximum=3, label="Steps", value=1, step=1, interactive=True)
extra_denoise = gr.Checkbox(True, label='Denoise')
keep_files = gr.Checkbox((False if IS_ZERO_GPU else True), label='Keep intermediate files', interactive=(False if IS_ZERO_GPU else True))
# 2. Audio mixing options
with gr.Accordion('Audio mixing options', open=False):
with gr.Row():
main_gain = gr.Slider(-20, 20, value=0, step=1, label='Main Vocals (dB)')
backup_gain = gr.Slider(-20, 20, value=0, step=1, label='Backup Vocals (dB)')
inst_gain = gr.Slider(-20, 20, value=0, step=1, label='Music (dB)')
with gr.Row():
reverb_rm_size = gr.Slider(0, 1, value=0.15, label='Reverb Room size')
reverb_wet = gr.Slider(0, 1, value=0.2, label='Reverb Wetness')
reverb_dry = gr.Slider(0, 1, value=0.8, label='Reverb Dryness')
reverb_damping = gr.Slider(0, 1, value=0.7, label='Reverb Damping')
output_format = gr.Dropdown(['mp3', 'wav'], value='mp3', label='Output file type')
# 3. Advanced mixing options
with gr.Accordion('Advanced mixing options', open=False):
with gr.Row():
soundgoodizer = gr.Slider(0, 1, value=0, step=0.05, label='Soundgoodizer (0=off, 1=max)')
with gr.Row():
highpass_freq = gr.Slider(0, 500, value=80, step=10, label='High-pass filter (Hz)')
lowpass_freq = gr.Slider(0, 20000, value=0, step=100, label='Low-pass filter (Hz, 0=off)')
with gr.Row():
low_shelf_gain = gr.Slider(-15, 15, value=0, step=0.5, label='Low shelf gain (dB)')
low_shelf_freq = gr.Slider(50, 500, value=200, step=10, label='Low shelf freq (Hz)')
mid_peak_gain = gr.Slider(-15, 15, value=0, step=0.5, label='Mid peak gain (dB)')
mid_peak_freq = gr.Slider(200, 5000, value=1000, step=50, label='Mid peak freq (Hz)')
mid_peak_q = gr.Slider(0.1, 5, value=1.0, step=0.1, label='Mid peak Q')
high_shelf_gain = gr.Slider(-15, 15, value=0, step=0.5, label='High shelf gain (dB)')
high_shelf_freq = gr.Slider(2000, 12000, value=5000, step=100, label='High shelf freq (Hz)')
with gr.Row():
noise_gate_threshold = gr.Slider(-100, 0, value=-55, step=1, label='Noise gate threshold (dB)')
noise_gate_ratio = gr.Slider(1, 20, value=10, step=0.5, label='Noise gate ratio')
compressor_threshold = gr.Slider(-60, 0, value=-15, step=1, label='Compressor threshold (dB)')
compressor_ratio = gr.Slider(1, 20, value=4, step=0.5, label='Compressor ratio')
limiter_ceiling = gr.Slider(-10, 0, value=-1, step=0.1, label='Limiter ceiling (dB)')
final_gain = gr.Slider(-12, 12, value=0, step=0.5, label='Final gain (dB)')
with gr.Row():
distortion_drive = gr.Slider(0, 30, value=0, step=0.5, label='Distortion drive (dB, 0=off)')
bitcrush_bits = gr.Slider(0, 16, value=0, step=0.5, label='Bitcrush bits (0=off, 16=none)')
clipping_threshold = gr.Slider(-24, 0, value=0, step=0.5, label='Clipping threshold (dB, 0=off)')
with gr.Row():
chorus_mix = gr.Slider(0, 1, value=0, step=0.05, label='Chorus mix (0=off)')
chorus_depth = gr.Slider(0, 1, value=0.25, step=0.05, label='Chorus depth')
chorus_rate = gr.Slider(0, 10, value=0.5, step=0.1, label='Chorus rate (Hz)')
with gr.Row():
phaser_mix = gr.Slider(0, 1, value=0, step=0.05, label='Phaser mix (0=off)')
phaser_depth = gr.Slider(0, 1, value=0.5, step=0.05, label='Phaser depth')
phaser_rate = gr.Slider(0, 10, value=0.5, step=0.1, label='Phaser rate (Hz)')
with gr.Row():
delay_seconds = gr.Slider(0, 2, value=0, step=0.05, label='Delay time (s, 0=off)')
delay_feedback = gr.Slider(0, 0.95, value=0.3, step=0.05, label='Delay feedback')
delay_mix = gr.Slider(0, 1, value=0, step=0.05, label='Delay mix (0=off)')
# 4. Backing vocal inference
with gr.Accordion('Backing Vocal Inference', open=False):
backup_vocal_infer = gr.Checkbox(False, label='Infer backing vocals with AI voice')
backup_vocal_pitch = gr.Slider(-12, 12, value=0, step=1, label='Backing vocal harmony pitch (semitones, 0=unison, +3/-3=harmony)')
# 5. Presets
with gr.Accordion('Presets', open=False):
if _HAS_MODULES and get_preset_manager() is not None:
preset_names_init = get_preset_manager().list_names()
else:
preset_names_init = []
with gr.Row():
preset_dropdown = gr.Dropdown(preset_names_init, label='Load Preset', interactive=True, scale=3)
preset_load_btn = gr.Button('Load', variant='primary', scale=1)
preset_refresh_btn = gr.Button('Refresh', variant='secondary', scale=1)
preset_description_box = gr.Textbox(label='Preset description', interactive=False, lines=2)
preset_dropdown.change(preset_describe, inputs=[preset_dropdown], outputs=[preset_description_box])
preset_refresh_btn.click(preset_refresh_list, outputs=[preset_dropdown, preset_description_box])
preset_load_btn.click(
preset_load_selected,
inputs=[preset_dropdown],
outputs=[
rvc_model, pitch, pitch_all, index_rate, filter_radius,
rms_mix_rate, protect, f0_method, crepe_hop_length, steps,
main_gain, backup_gain, inst_gain, reverb_rm_size, reverb_wet,
reverb_dry, reverb_damping, output_format, extra_denoise,
preset_description_box,
],
)
# --- Generate button + output ---
with gr.Row():
clear_btn = gr.ClearButton(value='Clear', components=[local_file, rvc_model])
generate_btn = gr.Button("Generate", variant='primary')
# Always use gr.Audio so the output is playable inline.
# type='filepath' lets us receive the file path as a string.
ai_cover = gr.Audio(label='AI Cover', type='filepath')
ref_btn.click(update_models_list, None, outputs=rvc_model)
is_webui = gr.Number(value=1, visible=False)
# Generate button: first resolve the song path via prepare_song_input,
# then call the pipeline with the resolved path.
# prepare_song_input raises gr.Error if inputs are missing, which
# stops the chain and shows a clean error to the user.
# Store the pipeline chain so we can add a .then() auto-refresh
# of the history tab after all tabs are defined.
_pipeline_chain = generate_btn.click(
prepare_song_input,
inputs=[local_file, song_input_path, rvc_model],
outputs=[song_input_path],
).then(
song_cover_pipeline,
inputs=[song_input_path, rvc_model, pitch, keep_files, is_webui, main_gain, backup_gain,
inst_gain, index_rate, filter_radius, rms_mix_rate, f0_method, crepe_hop_length,
protect, pitch_all, reverb_rm_size, reverb_wet, reverb_dry, reverb_damping,
output_format, extra_denoise, steps,
highpass_freq, lowpass_freq,
low_shelf_gain, low_shelf_freq,
mid_peak_gain, mid_peak_freq, mid_peak_q,
high_shelf_gain, high_shelf_freq,
noise_gate_threshold, noise_gate_ratio,
compressor_threshold, compressor_ratio,
limiter_ceiling, final_gain,
soundgoodizer,
distortion_drive,
bitcrush_bits,
chorus_depth, chorus_rate, chorus_mix,
delay_seconds, delay_feedback, delay_mix,
phaser_depth, phaser_rate, phaser_mix,
clipping_threshold,
backup_vocal_infer, backup_vocal_pitch],
outputs=[ai_cover],
)
# Clear button resets all controls including advanced mixing + backing vocals
clear_btn.click(
lambda: [0, 0, 0, 0, 0.5, 3, 0.25, 0.33, 'rmvpe+', 128, 0, 0.15, 0.2, 0.8, 0.7, 'mp3', None, True, 1, "",
80, 0, 0, 200, 0, 1000, 1.0, 0, 5000, -55, 10, -15, 4, -1, 0,
0, 0, 0, 0.25, 0.5, 0, 0, 0.3, 0, 0.5, 0.5, 0, 0,
False, 0],
outputs=[pitch, main_gain, backup_gain, inst_gain, index_rate, filter_radius, rms_mix_rate,
protect, f0_method, crepe_hop_length, pitch_all, reverb_rm_size, reverb_wet,
reverb_dry, reverb_damping, output_format, ai_cover, extra_denoise, steps, song_input_path,
highpass_freq, lowpass_freq, low_shelf_gain, low_shelf_freq,
mid_peak_gain, mid_peak_freq, mid_peak_q, high_shelf_gain, high_shelf_freq,
noise_gate_threshold, noise_gate_ratio, compressor_threshold, compressor_ratio,
limiter_ceiling, final_gain,
soundgoodizer,
distortion_drive,
bitcrush_bits,
chorus_depth, chorus_rate, chorus_mix,
delay_seconds, delay_feedback, delay_mix,
phaser_depth, phaser_rate, phaser_mix,
clipping_threshold,
backup_vocal_infer, backup_vocal_pitch],
)
# ============================================
# DOWNLOAD MODEL TAB
# ============================================
with gr.Tab('Download model'):
with gr.Tab('From HuggingFace/Pixeldrain URL'):
with gr.Row():
model_zip_link = gr.Text(label='Download link to model', info='Should be a zip file containing a .pth model file and an optional .index file.')
model_name = gr.Text(label='Name your model', info='Give your new model a unique name from your other voice models.')
with gr.Row():
download_btn = gr.Button('Download', variant='primary', scale=19)
dl_output_message = gr.Text(label='Output Message', interactive=False, scale=20)
download_btn.click(download_online_model, inputs=[model_zip_link, model_name], outputs=dl_output_message)
gr.Markdown('## Input Examples')
gr.Examples(
[
['https://huggingface.co/MrDawg/ToothBrushing/resolve/main/ToothBrushing.zip?download=true', 'ToothBrushing'],
['https://huggingface.co/sail-rvc/Aldeano_Minecraft__RVC_V2_-_500_Epochs_/resolve/main/model.pth?download=true, https://huggingface.co/sail-rvc/Aldeano_Minecraft__RVC_V2_-_500_Epochs_/resolve/main/model.index?download=true', 'Minecraft_Villager'],
['https://huggingface.co/phant0m4r/LiSA/resolve/main/LiSA.zip', 'Lisa'],
['https://pixeldrain.com/u/3tJmABXA', 'Gura'],
['https://huggingface.co/Kit-Lemonfoot/kitlemonfoot_rvc_models/resolve/main/AZKi%20(Hybrid).zip', 'Azki']
],
[model_zip_link, model_name],
[],
download_online_model,
cache_examples=False,
)
with gr.Tab('From Public Index'):
gr.Markdown('## How to use')
gr.Markdown('- Click Initialize public models table')
gr.Markdown('- Filter models using tags or search bar')
gr.Markdown('- Select a row to autofill the download link and model name')
gr.Markdown('- Click Download')
with gr.Row():
pub_zip_link = gr.Text(label='Download link to model')
pub_model_name = gr.Text(label='Model name')
with gr.Row():
download_pub_btn = gr.Button('Download', variant='primary', scale=19)
pub_dl_output_message = gr.Text(label='Output Message', interactive=False, scale=20)
filter_tags = gr.CheckboxGroup(value=[], label='Show voice models with tags', choices=[])
search_query = gr.Text(label='Search')
load_public_models_button = gr.Button(value='Initialize public models table', variant='primary')
public_models_table = gr.DataFrame(value=[], headers=['Model Name', 'Description', 'Credit', 'URL', 'Tags'], label='Available Public Models', interactive=False)
public_models_table.select(pub_dl_autofill, inputs=[public_models_table], outputs=[pub_zip_link, pub_model_name])
load_public_models_button.click(load_public_models, outputs=[public_models_table, filter_tags])
search_query.change(filter_models, inputs=[filter_tags, search_query], outputs=public_models_table)
filter_tags.change(filter_models, inputs=[filter_tags, search_query], outputs=public_models_table)
download_pub_btn.click(download_online_model, inputs=[pub_zip_link, pub_model_name], outputs=pub_dl_output_message)
# ============================================
# UPLOAD MODEL TAB
# ============================================
with gr.Tab('Upload model'):
gr.Markdown('## Upload locally trained RVC v2 model and index file')
gr.Markdown('- Find model file (weights folder) and optional index file (logs/[name] folder)')
gr.Markdown('- Compress files into zip file')
gr.Markdown('- Upload zip file and give unique name for voice')
gr.Markdown('- Click Upload model')
with gr.Row():
with gr.Column():
zip_file = gr.File(label='Zip file')
local_model_name = gr.Text(label='Model name')
with gr.Row():
model_upload_button = gr.Button('Upload model', variant='primary', scale=19)
local_upload_output_message = gr.Text(label='Output Message', interactive=False, scale=20)
model_upload_button.click(upload_local_model, inputs=[zip_file, local_model_name], outputs=local_upload_output_message)
# ============================================
# PRESET MANAGER TAB (NEW)
# ============================================
with gr.Tab('Preset Manager'):
gr.Markdown("## Preset Manager")
gr.Markdown("Save your current settings (voice model + pitch + reverb + gains + etc.) as a named preset, then load it later in one click from the Generate tab.")
if _HAS_MODULES and get_preset_manager() is not None:
pm_init_names = get_preset_manager().list_names()
else:
pm_init_names = []
with gr.Accordion("Load / Delete Preset", open=True):
with gr.Row():
pm_dropdown = gr.Dropdown(pm_init_names, label='Presets', interactive=True, scale=3)
pm_refresh_btn = gr.Button('Refresh List', variant='secondary', scale=1)
pm_delete_btn = gr.Button('Delete Selected', variant='stop', scale=1)
pm_reset_btn = gr.Button('Restore Built-ins', variant='secondary', scale=1)
pm_description = gr.Textbox(label='Description', interactive=False, lines=3)
pm_status = gr.Textbox(label='Status', interactive=False, lines=1)
pm_dropdown.change(preset_describe, inputs=[pm_dropdown], outputs=[pm_description])
pm_refresh_btn.click(preset_refresh_list, outputs=[pm_dropdown, pm_description])
pm_delete_btn.click(preset_delete_selected, inputs=[pm_dropdown], outputs=[pm_status, pm_dropdown])
pm_reset_btn.click(preset_reset_builtin, outputs=[pm_status, pm_dropdown])
with gr.Accordion("Save Current Settings as Preset", open=False):
gr.Markdown("Fill in the values below to save a new preset. (You can also use the Load Preset button in the Generate tab after creating it here.)")
save_name = gr.Textbox(label="Preset name", placeholder="e.g. My Smooth Female Voice")
save_desc = gr.Textbox(label="Description", lines=2, placeholder="What does this preset do?")
with gr.Row():
save_voice_model = gr.Textbox(label="Voice model name (must match a folder in rvc_models)")
with gr.Row():
save_pitch = gr.Slider(-3, 3, value=0, step=1, label='Pitch (vocals)')
save_pitch_all = gr.Slider(-12, 12, value=0, step=1, label='Pitch (all)')
with gr.Row():
save_index_rate = gr.Slider(0, 1, value=0.5, label='Index rate')
save_filter_radius = gr.Slider(0, 7, value=3, step=1, label='Filter radius')
save_rms = gr.Slider(0, 1, value=0.25, label='RMS mix rate')
save_protect = gr.Slider(0, 0.5, value=0.33, label='Protect')
with gr.Row():
save_f0 = gr.Dropdown(['rmvpe+', 'rmvpe', 'mangio-crepe'], value='rmvpe+', label='F0 method')
save_crepe_hop = gr.Slider(32, 320, value=128, step=1, label='Crepe hop')
save_steps = gr.Slider(1, 3, value=1, step=1, label='Steps')
with gr.Row():
save_main_gain = gr.Slider(-20, 20, value=0, step=1, label='Main gain (dB)')
save_backup_gain = gr.Slider(-20, 20, value=0, step=1, label='Backup gain (dB)')
save_inst_gain = gr.Slider(-20, 20, value=0, step=1, label='Inst gain (dB)')
with gr.Row():
save_rev_size = gr.Slider(0, 1, value=0.15, label='Reverb room size')
save_rev_wet = gr.Slider(0, 1, value=0.2, label='Reverb wet')
save_rev_dry = gr.Slider(0, 1, value=0.8, label='Reverb dry')
save_rev_damp = gr.Slider(0, 1, value=0.7, label='Reverb damping')
with gr.Row():
save_output_format = gr.Dropdown(['mp3', 'wav'], value='mp3', label='Output format')
save_extra_denoise = gr.Checkbox(True, label='Extra denoise')
save_btn = gr.Button('Save Preset', variant='primary')
save_status = gr.Textbox(label='Status', interactive=False, lines=1)
save_btn.click(
preset_save_current,
inputs=[
save_name, save_desc, save_voice_model, save_pitch, save_pitch_all,
save_index_rate, save_filter_radius, save_rms, save_protect,
save_f0, save_crepe_hop, save_steps,
save_main_gain, save_backup_gain, save_inst_gain,
save_rev_size, save_rev_wet, save_rev_dry, save_rev_damp,
save_output_format, save_extra_denoise,
],
outputs=[save_status, pm_dropdown],
)
# ============================================
# HISTORY TAB (NEW)
# ============================================
with gr.Tab('History'):
with gr.Row():
history_refresh_btn = gr.Button('Refresh', variant='primary')
history_stats_btn = gr.Button('Show Stats', variant='secondary')
history_clear_btn = gr.Button('Clear History', variant='stop')
history_clear_files_cb = gr.Checkbox(False, label='Also delete cover files')
history_summary = gr.Textbox(label='Summary', interactive=False, lines=1)
history_table = gr.DataFrame(
value=[],
headers=['When', 'Song', 'Voice Model', 'Pitch', 'Pitch All', 'Duration (s)', 'Format', 'File Exists', 'Path'],
label='History (newest first) - click a row to play it',
interactive=False,
wrap=True,
)
history_player = gr.Audio(label='Selected cover (click a row above to play)', interactive=False, type='filepath')
history_status = gr.Textbox(label='Status', interactive=False, lines=2)
# history_refresh now returns 3 values: (table, summary, status)
history_refresh_btn.click(
history_refresh,
outputs=[history_table, history_summary, history_status],
)
history_stats_btn.click(history_get_stats, outputs=[history_status])
# history_clear now returns 4 values: (status, table, summary, player)
history_clear_btn.click(
history_clear,
inputs=[history_clear_files_cb],
outputs=[history_status, history_table, history_summary, history_player],
)
# Click a row to play it
history_table.select(
history_play_selected,
inputs=[history_table],
outputs=[history_player],
)
# ============================================
# SYSTEM / CACHE STATS TAB (NEW)
# ============================================
with gr.Tab('System'):
gr.Markdown("## System & Cache")
gr.Markdown("Monitor model cache hit rate and clear it to free memory.")
with gr.Row():
cache_refresh_btn = gr.Button('Refresh Cache Stats', variant='primary')
cache_clear_btn = gr.Button('Clear Cache', variant='stop')
cache_stats_box = gr.Textbox(label='Model Cache', interactive=False, lines=12, max_lines=15, value="Click Refresh to view.")
cache_refresh_btn.click(cache_stats_refresh, outputs=[cache_stats_box])
cache_clear_btn.click(cache_clear, outputs=[cache_stats_box])
gr.Markdown("---")
gr.Markdown("### Disk usage")
disk_usage_box = gr.Textbox(label='Disk usage', interactive=False, lines=8, value="Click button below to compute.")
disk_refresh_btn = gr.Button('Compute Disk Usage', variant='secondary')
disk_refresh_btn.click(show_disk_usage, outputs=[disk_usage_box])
# ============================================
# AUDIO FOLDERS MANAGEMENT TAB
# ============================================
with gr.Tab('Audio Folders'):
gr.Markdown("## Manage Audio Folders")
gr.Markdown(f"Audio library location: `{AUDIO_FOLDERS_DIR}`")
gr.Markdown(f"The first entry **{ROOT_FOLDER_LABEL}** represents files placed directly in this folder. You can also create subfolders to organize your library.")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Current Audio Folders")
_af_init_folders = get_audio_folders()
_af_init_value = _af_init_folders[0] if _af_init_folders else None
current_folders = gr.Dropdown(
choices=_af_init_folders,
value=_af_init_value,
label='Available Folders',
interactive=True,
)
refresh_folders_display = gr.Button('Refresh Folders List', variant='secondary')
refresh_folders_display.click(refresh_audio_folders, outputs=current_folders)
gr.Markdown("### Create New Folder")
new_folder_name = gr.Textbox(label='New folder name', placeholder='e.g. My Songs')
create_folder_btn = gr.Button('Create Folder', variant='primary')
create_folder_status = gr.Textbox(label='Status', interactive=False, lines=1)
create_folder_btn.click(
create_audio_folder,
inputs=[new_folder_name],
outputs=[create_folder_status, current_folders],
)
gr.Markdown("### Upload Audio to Selected Folder")
upload_target_folder = gr.Dropdown(
choices=_af_init_folders,
value=_af_init_value,
label='Upload to folder',
interactive=True,
)
upload_files_input = gr.File(
label='Select audio file(s) to upload',
file_count='multiple',
file_types=[ext.lstrip('.') for ext in AUDIO_EXTENSIONS if ext.startswith('.')],
)
upload_files_btn = gr.Button('Upload to Folder', variant='primary')
upload_status = gr.Textbox(label='Upload status', interactive=False, lines=2)
upload_files_btn.click(
upload_to_audio_folder,
inputs=[upload_files_input, upload_target_folder],
outputs=[upload_status, upload_target_folder],
).then(
# Also refresh the main folder list after upload
refresh_audio_folders,
outputs=current_folders,
)
gr.Markdown("### File Statistics")
folder_stats = gr.Textbox(label='Statistics', interactive=False, lines=20, max_lines=25)
get_stats_btn = gr.Button('Show Statistics', variant='secondary')
get_stats_btn.click(show_folder_stats, outputs=folder_stats)
with gr.Column(scale=2):
gr.Markdown("### How to Use Audio Folders")
gr.Markdown(f"""
1. **Quick start:** Upload audio directly via the **Upload Audio to Selected Folder** section on the left (the **{ROOT_FOLDER_LABEL}** entry puts files at the root).
2. (Optional) Create subfolders to organize songs by artist, genre, etc.
3. Go to the **Generate** tab.
4. Click **"Select from Audio Folders"**.
5. Choose a folder from the dropdown (the audio files list will refresh automatically).
6. Pick an audio file from the second dropdown.
7. Click **"Select Audio File"** to load it into the input.
8. Proceed with generating your AI cover.
""")
gr.Markdown("### Supported Audio Formats")
gr.Markdown(f"**Common:** MP3, MP2, WAV, FLAC, M4A, AAC, OGG, WMA, ALAC, AIFF, OPUS, AMR \n**Additional:** 3GP, 3G2, AC3, EAC3, APE, AU, SND, CAF, DTS, FLV, GSM, MKA, MLP, MPC, PCM, QCP, RA, RM, RAM, SHN, SPX, TTA, VOC, VOX, WV")
gr.Markdown("### Tips")
gr.Markdown("""
- Files uploaded to **Main Library** go directly into the audio_folders root.
- Subfolders inside your audio folders are scanned recursively.
- The audio files load into the same input as file uploads on the Generate tab.
- Convert unsupported formats to MP3, WAV, or FLAC for best compatibility.
- Use Refresh after manually adding files via the filesystem.
""")
# ============================================
# AUTO-REFRESH HISTORY AFTER GENERATION
# ============================================
# After a cover is generated, auto-refresh the history tab so the
# new entry appears without the user needing to click Refresh.
# Chained off the pipeline .then() so it runs only on success.
_pipeline_chain.then(
history_refresh,
outputs=[history_table, history_summary, history_status],
)
# ============================================
# RETURN APP (caller is responsible for launching)
# ============================================
return app
# ============================================
# ENTRY POINT
# ============================================
if __name__ == '__main__':
parser = ArgumentParser(description='Generate a AI cover song in the song_output/id directory.', add_help=True)
parser.add_argument("--share", action="store_true", dest="share_enabled", default=False, help="Enable sharing")
parser.add_argument("--builtin-player", action="store_true", default=False, help="Use the builtin audio player")
parser.add_argument("--listen", action="store_true", default=False, help="Make the WebUI reachable from your local network.")
parser.add_argument('--listen-host', type=str, help='The hostname that the server will use.')
parser.add_argument('--listen-port', type=int, help='The listening port that the server will use.')
parser.add_argument('--theme', type=str, default="NoCrypt/miku", help='Set the theme (default: NoCrypt/miku)')
parser.add_argument("--ssr", action="store_true", help="Enable SSR (Server-Side Rendering)")
args = parser.parse_args()
app = build_app(args)
app.launch(
share=args.share_enabled,
debug=args.share_enabled,
show_error=True,
server_name=None if not args.listen else (args.listen_host or '0.0.0.0'),
server_port=args.listen_port,
ssr_mode=args.ssr,
)