midi2wav / app.py
namelessai's picture
Update app.py
9d5b0ac verified
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)