import io import js from pyodide.ffi import create_proxy from js import console, Uint8Array, File, URL, document, window # Import original engines # We assume these files are present in the virtual filesystem from audio_engine import get_spectrogram_data from shape_donut import generate_donut_stl from shape_heart import generate_heart_stl async def process_audio(event): """ Called when the user clicks 'Generate'. Reads the file from the JS input, saves it to the virtual JS FS, runs the original python engines, and returns the STL. """ try: # 1. UI Updates document.getElementById("status").innerText = "Starting Processing..." update_progress("Reading File...") # 2. Get Input Data file_input = document.getElementById("audioFile") if file_input.files.length == 0: js.alert("Please select a file first.") return file_obj = file_input.files.item(0) # Check for WAV extension if not file_obj.name.lower().endswith('.wav'): js.alert("Invalid file type. Please upload a .wav file.\n\nTip: You can use an online converter to change your audio file to .wav format.") document.getElementById("status").innerText = "Error: Invalid file type. Please use a .wav file." return array_buffer = await file_obj.arrayBuffer() # Convert JS ArrayBuffer to Python Bytes data_bytes = array_buffer.to_bytes() # 3. Simulate File on Disk (Original engine expects a path) # We write to the Emscripten virtual filesystem input_filename = "temp_input.wav" with open(input_filename, "wb") as f: f.write(data_bytes) # 4. Parse Parameters shape = document.getElementById("shapeType").value diameter_val = int(document.getElementById("diameter").value) amp_val = float(document.getElementById("ampFactor").value) base_thickness_val = float(document.getElementById("baseThickness").value) sigma_val = 2.6 # Fixed smoothing # 5. Run Original Audio Engine update_progress("Analyzing Audio (Librosa)...") # Note: audio_engine.py calls librosa.load(path). # Since we saved 'temp_input.wav' locally, this works naturally. grid = get_spectrogram_data(input_filename, max_points=150) # 6. Run Original Shape Engine update_progress("Generating 3D Mesh...") if shape == "heart": # Defaults from main.py max_height = diameter_val * 0.25 stl_mesh = generate_heart_stl(grid, diameter=diameter_val, max_height=max_height, base_thickness=base_thickness_val, smoothing=sigma_val, amp_factor=amp_val) else: # Defaults fro main.py hole_diameter = diameter_val * 0.3 max_height = diameter_val * 0.25 stl_mesh = generate_donut_stl(grid, diameter=diameter_val, hole_diameter=hole_diameter, max_height=max_height, base_thickness=base_thickness_val, smoothing=sigma_val, amp_factor=amp_val) # 7. Save STL to Memory & Download update_progress("Finalizing...") output_io = io.BytesIO() stl_mesh.save("out.stl", fh=output_io) stl_data = output_io.getvalue() # Trigger Download download_file(stl_data, f"voice_message_{shape}_client.stl") # Track Usage in GA4 try: console.log(f"DEBUG: Tracking generation event for {shape}") window.trackGeneration(shape) except Exception as e: console.error(f"Analytics Error: {str(e)}") update_progress("Done!") document.getElementById("success-container").classList.remove("hidden") document.getElementById("runButton").disabled = False document.getElementById("runButton").innerText = "Generate 3D Model" except Exception as e: console.error(str(e)) document.getElementById("status").innerText = f"Error: {e}" document.getElementById("runButton").disabled = False def update_progress(msg): document.getElementById("status").innerText = msg # Yield control to UI # In PyScript async functions, we trust the event loop. def download_file(data, filename): stream = Uint8Array.new(data) blob = js.Blob.new([stream], { "type": "application/sla" }) url = URL.createObjectURL(blob) link = document.createElement("a") link.href = url link.download = filename document.body.appendChild(link) link.click() document.body.removeChild(link) # Expose function to JS # In PyScript 2024.x+, we can bind via 'py-click' or manually. # We'll use manual binding for clarity in the script tag. def setup(): click_proxy = create_proxy(process_audio) document.getElementById("runButton").addEventListener("click", click_proxy) # Enable button now that Python is ready document.getElementById("runButton").disabled = False document.getElementById("loading-overlay").style.display = "none" print("Python Ready") setup()