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, )