Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from midi2audio import FluidSynth | |
| import os | |
| import tempfile | |
| import zipfile | |
| import glob | |
| import shutil | |
| import logging | |
| # --- Setup pretty logging --- | |
| # This will make console logs much cleaner and more informative | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s - [%(levelname)s] - %(message)s", | |
| datefmt="%Y-%m-%d %H:%M:%S" | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # --- End logging setup --- | |
| # --- Configuration --- | |
| SOUNDFONT_FILE = "timbres_of_heaven.sf2" | |
| EXAMPLES_ZIP = "examples.zip" | |
| EXAMPLES_DIR = "midi_examples" | |
| # --- End Configuration --- | |
| def check_fluidsynth_dependency(): | |
| """ | |
| Checks if FluidSynth is installed and returns a status message for the UI. | |
| """ | |
| if shutil.which("fluidsynth") is None: | |
| logger.warning("'fluidsynth' command not found in system PATH.") | |
| logger.warning("This app will fail if not run in the provided Docker container.") | |
| logger.warning("If running locally, please install FluidSynth.") | |
| return ("⚠️ **Warning:** 'fluidsynth' command not found. " | |
| "The app may not work unless run via Docker.") | |
| else: | |
| logger.info("'fluidsynth' dependency check passed.") | |
| return "✅ FluidSynth dependency check passed." | |
| def extract_examples(): | |
| """ | |
| Extracts examples.zip and returns a list of files and a status message. | |
| """ | |
| if not os.path.exists(EXAMPLES_ZIP): | |
| logger.warning(f"'{EXAMPLES_ZIP}' not found. No examples will be loaded.") | |
| return [], f"⚠️ **Warning:** `{EXAMPLES_ZIP}` not found. No examples will be loaded." | |
| if not os.path.exists(EXAMPLES_DIR): | |
| os.makedirs(EXAMPLES_DIR) | |
| logger.info(f"Created examples directory: {EXAMPLES_DIR}") | |
| try: | |
| with zipfile.ZipFile(EXAMPLES_ZIP, 'r') as zip_ref: | |
| zip_ref.extractall(EXAMPLES_DIR) | |
| midi_files = glob.glob(os.path.join(EXAMPLES_DIR, "**/*.midi"), recursive=True) | |
| kar_files = glob.glob(os.path.join(EXAMPLES_DIR, "**/*.kar"), recursive=True) | |
| mid_files = glob.glob(os.path.join(EXAMPLES_DIR, "**/*.mid"), recursive=True) # <-- Added this line | |
| example_list = midi_files + kar_files + mid_files # <-- Updated this line | |
| example_paths = [[example] for example in example_list] | |
| if not example_paths: | |
| logger.warning(f"No .midi, .kar, or .mid files found in '{EXAMPLES_ZIP}'.") # <-- Updated this line | |
| return [], f"⚠️ **Warning:** No `.midi`, `.kar`, or `.mid` files found in `{EXAMPLES_ZIP}`." # <-- Updated this line | |
| logger.info(f"Loaded {len(example_paths)} examples from {EXAMPLES_ZIP}.") | |
| return example_paths, f"✅ Loaded {len(example_paths)} examples from `{EXAMPLES_ZIP}`." | |
| except zipfile.BadZipFile: | |
| logger.error(f"'{EXAMPLES_ZIP}' is not a valid zip file.") | |
| return [], f"⛔ **Error:** `{EXAMPLES_ZIP}` is not a valid zip file." | |
| except Exception as e: | |
| logger.error(f"Error extracting examples: {e}", exc_info=True) | |
| return [], f"⛔ **Error:** Could not extract examples. See console logs." | |
| def render_midi(midi_file_path, progress=gr.Progress(track_tqdm=True)): | |
| """ | |
| Renders a MIDI/KAR file to audio, now with progress updates and feedback. | |
| The input `midi_file_path` is always a string filepath. | |
| """ | |
| try: | |
| # --- 1. Validation and Setup (Progress: 0% -> 20%) --- | |
| progress(0, desc="Starting render...") | |
| if not os.path.exists(SOUNDFONT_FILE): | |
| logger.error(f"SoundFont file not found at '{SOUNDFONT_FILE}'") | |
| raise gr.Error( | |
| f"SoundFont file not found! The app is looking for '{SOUNDFONT_FILE}'. " | |
| f"Please add it and restart." | |
| ) | |
| progress(0.1, desc="SoundFont found. Checking input...") | |
| # Simplified logic: Input is always a filepath string | |
| if isinstance(midi_file_path, str): | |
| input_midi_path = midi_file_path | |
| logger.info(f"Processing input file: {input_midi_path}") | |
| else: | |
| logger.error(f"Invalid input file type: {type(midi_file_path)}") | |
| raise gr.Error(f"Invalid input file type: {type(midi_file_path)}") | |
| progress(0.2, desc="Input file OK. Preparing output...") | |
| # --- 2. Create Temp File (Progress: 20% -> 30%) --- | |
| with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_out: | |
| output_wav_path = tmp_out.name | |
| progress(0.3, desc="Temporary output file created.") | |
| # --- 3. Run FluidSynth (Progress: 30% -> 90%) --- | |
| logger.info(f"Initializing FluidSynth with {SOUNDFONT_FILE}") | |
| fs = FluidSynth(SOUNDFONT_FILE) | |
| progress(0.5, desc="Rendering audio... (This may take a moment)") | |
| # This is the main rendering step | |
| fs.midi_to_audio(input_midi_path, output_wav_path) | |
| logger.info(f"Audio rendered successfully to {output_wav_path}") | |
| progress(0.9, desc="Audio rendered. Finalizing...") | |
| # --- 4. Success and Cleanup (Progress: 90% -> 100%) --- | |
| gr.Info("Render complete!") # Show a success pop-up | |
| progress(1.0, desc="Done!") | |
| return output_wav_path | |
| except Exception as e: | |
| # Log the full error to the console for debugging | |
| logger.error(f"Failed to render audio: {str(e)}", exc_info=True) | |
| # Show a user-friendly error in the UI | |
| raise gr.Error(f"Failed to render audio. Error: {str(e)}. " | |
| "This can happen if FluidSynth is not installed " | |
| "or if the MIDI file is corrupt.") | |
| # --- Run Startup Checks --- | |
| fluidsynth_status = check_fluidsynth_dependency() | |
| example_list, example_status = extract_examples() | |
| initial_status_markdown = f""" | |
| **System Status:** | |
| - {fluidsynth_status} | |
| - {example_status} | |
| """ | |
| # --- End Startup Checks --- | |
| # --- Create the Gradio Interface --- | |
| with gr.Blocks() as app: # <-- I've removed theme=gr.themes.Soft() | |
| gr.Markdown( | |
| """ | |
| # 🎵 MIDI & KAR Audio Renderer | |
| Upload a `.midi` or `.kar` file to synthesize it into audio | |
| using the **Timbres of Heaven** SoundFont. | |
| """ | |
| ) | |
| # New Status Display | |
| with gr.Accordion("Show System Status", open=False): | |
| status_display = gr.Markdown(initial_status_markdown, elem_id="status-display") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| file_input = gr.File( | |
| label="Upload .midi, .kar, or .mid file", # <-- Updated this line | |
| file_types=[".midi", ".kar", ".mid"], # <-- Updated this line | |
| type="filepath" # <-- Changed from "file" to "filepath" | |
| ) | |
| submit_btn = gr.Button("Render Audio", variant="primary") | |
| with gr.Column(scale=2): | |
| audio_output = gr.Audio( | |
| label="Rendered Audio", | |
| type="filepath" | |
| ) | |
| # Connect the button click to the render function | |
| # Gradio automatically sees the 'progress' argument in render_midi | |
| # and will provide the progress bar component to it. | |
| submit_btn.click( | |
| fn=render_midi, | |
| inputs=file_input, | |
| outputs=audio_output | |
| ) | |
| gr.Examples( | |
| examples=example_list, | |
| inputs=file_input, | |
| outputs=audio_output, | |
| fn=render_midi, | |
| cache_examples=True, | |
| label="Click an example to render it!" if example_list else "No examples found." | |
| ) | |
| # --- Launch the App --- | |
| if __name__ == "__main__": | |
| logger.info("Starting Gradio application...") | |
| app.launch(server_name="0.0.0.0", server_port=7860) |