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)