Spaces:
Paused
Paused
File size: 5,753 Bytes
0403886 52ad754 0403886 52ad754 0403886 52ad754 0403886 52ad754 0403886 52ad754 0403886 52ad754 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | #!/usr/bin/env python3
"""
Gradio app to convert one or more MIDI files into Geometry Dash .gmd files
(using midi_to_gd.py). For each uploaded MIDI the app will create a .gmd
file (plist wrapper with compressed k4 level string) and return a zip
containing all generated .gmd files.
"""
import os
import sys
import tempfile
import zipfile
import traceback
from typing import List, Tuple
HERE = os.path.dirname(os.path.abspath(__file__))
if HERE not in sys.path:
sys.path.insert(0, HERE)
try:
import gradio as gr
except Exception as e:
raise RuntimeError("Gradio is required. Install with: pip install gradio") from e
# Import functions from the provided script
try:
from midi_to_gd import (
parse_midi_notes,
generate_gd_level_objects,
build_full_level_string,
encrypt_level_string,
)
except Exception as e:
raise RuntimeError("Failed to import from midi_to_gd.py. Ensure the file is in the same directory.") from e
def _get_local_path_from_gr_file(f) -> str:
"""
Accepts the various shapes Gradio may pass:
- a filesystem path string
- a dict like {'name': '/tmp/....'}
- a list of the above (we only call this per-file)
Returns a single filesystem path string or empty if not found.
"""
if not f:
return ""
if isinstance(f, str):
return f
if isinstance(f, (list, tuple)) and f:
# take first element
return _get_local_path_from_gr_file(f[0])
if isinstance(f, dict):
# Gradio sometimes returns {'name': '/tmp/...'}
return f.get("name") or f.get("file") or ""
# Fallback: try attribute .name
return getattr(f, "name", "")
def convert_midis_to_zip(midi_files: List, beats_scale: float = 2.0, name_prefix: str = "") -> Tuple[str, str]:
"""
Process uploaded midi files (list), generate .gmd files for each, and return
a path to a zip archive and a status text message.
Returns:
(zip_file_path, status_message)
"""
try:
if not midi_files:
return None, "No MIDI files uploaded."
out_dir = tempfile.mkdtemp(prefix="gd_midis_")
created_files = []
# midi_files is expected to be a list of file paths (type="filepath")
for idx, f in enumerate(midi_files, start=1):
midi_path = _get_local_path_from_gr_file(f)
if not os.path.exists(midi_path):
return None, f"Uploaded file not found on disk: {midi_path}"
base = os.path.splitext(os.path.basename(midi_path))[0]
level_name = f"{name_prefix}{'_' if name_prefix else ''}{base}" if name_prefix else base
notes = parse_midi_notes(midi_path)
raw_objects = generate_gd_level_objects(notes, blocks_per_beat=beats_scale)
full_level_data = build_full_level_string(raw_objects)
encrypted_k4 = encrypt_level_string(full_level_data)
gmd_content = f"""<?xml version="1.0"?>
<plist version="1.0" gjver="2.0">
<dict>
<key>kCEK</key><i>4</i>
<key>k2</key><s>{level_name}</s>
<key>k3</key><s>MIDI converted Piano Roll</s>
<key>k4</key><s>{encrypted_k4}</s>
<key>k5</key><s>Player</s>
</dict>
</plist>"""
out_path = os.path.join(out_dir, f"{level_name}.gmd")
with open(out_path, "w", encoding="utf-8") as wf:
wf.write(gmd_content)
created_files.append(out_path)
zip_path = os.path.join(out_dir, "gd_levels.zip")
with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as zf:
for p in created_files:
zf.write(p, arcname=os.path.basename(p))
status_lines = [
f"Generated {len(created_files)} .gmd file(s).",
f"ZIP archive: {os.path.basename(zip_path)}",
"Files:",
] + [f"- {os.path.basename(p)}" for p in created_files]
return zip_path, "\n".join(status_lines)
except Exception:
tb = traceback.format_exc()
return None, f"Error during conversion:\n{tb}"
def launch_ui():
with gr.Blocks() as demo:
gr.Markdown("# MIDI → Geometry Dash (.gmd) Converter")
gr.Markdown(
"Upload one or more MIDI files. The app will convert each MIDI into a Geometry Dash .gmd "
"file (plist wrapper with compressed level string) and return a ZIP archive containing all generated .gmd files."
)
with gr.Row():
# NOTE: type="filepath" is the correct modern API. 'file' is invalid.
midi_input = gr.File(label="Upload MIDI files (.mid/.midi)", file_count="multiple", type="filepath")
settings_col = gr.Column(scale=1)
beats_scale = gr.Number(value=2.0, label="Blocks per beat (blocks/quarter-note)", precision=2)
name_prefix = gr.Textbox(label="Optional level name prefix", placeholder="Prefix (optional)")
convert_btn = gr.Button("Convert and Download ZIP")
output_zip = gr.File(label="Download ZIP of .gmd files")
status = gr.Textbox(label="Status", interactive=False, lines=6)
def _on_click(files, beats, prefix):
zip_path, status_text = convert_midis_to_zip(files, beats, prefix)
return zip_path, status_text
convert_btn.click(fn=_on_click, inputs=[midi_input, beats_scale, name_prefix], outputs=[output_zip, status])
gr.Markdown("Notes: mido is required to parse MIDI files. Install with `pip install mido`.")
gr.Markdown("If the app cannot find your local CCLocalLevels.dat or you just want the .gmd files, use the ZIP to import them with your editor.")
demo.launch()
if __name__ == "__main__":
launch_ui()
|