""" app.py — Gradio front-end for tape_restore.py Deploy: put this file, tape_restore.py, requirements.txt, and README.md in the root of a Hugging Face Space with SDK = Gradio. """ import os import tempfile import traceback import gradio as gr import numpy as np import tape_restore as tr class Args: """Lightweight stand-in for the argparse.Namespace tape_restore.restore() expects.""" pass def run_restoration( audio_path, use_noise_sample, noise_start, noise_end, hiss_strength, click_threshold, hum_freq, wow_flutter, do_declick, do_dehum, do_dehiss, do_eq, do_dynamics, progress=gr.Progress(track_tqdm=False), ): if audio_path is None: raise gr.Error("Upload an audio file first.") try: progress(0.05, desc="Loading audio...") audio, sr = tr.load_audio(audio_path) duration = len(audio) / sr if duration > 20 * 60: raise gr.Error( f"File is {duration/60:.1f} minutes long. This demo Space caps " f"input at 20 minutes to avoid CPU timeouts — for longer masters, " f"run tape_restore.py locally instead." ) args = Args() args.no_declick = not do_declick args.click_threshold = click_threshold args.no_hum = not do_dehum args.hum_freq = float(hum_freq) args.no_hiss = not do_dehiss args.hiss_strength = hiss_strength args.noise_sample = ( [noise_start, noise_end] if use_noise_sample and noise_end > noise_start else None ) args.wow_flutter = wow_flutter args.no_eq = not do_eq args.no_dynamics = not do_dynamics progress(0.2, desc="Restoring (this can take a while on CPU)...") restored = tr.restore(audio, sr, args) progress(0.9, desc="Writing output...") out_path = os.path.join( tempfile.gettempdir(), f"restored_{next(tempfile._get_candidate_names())}.wav" ) tr.save_audio(out_path, restored, sr) progress(1.0, desc="Done") return out_path, "Restoration complete." except gr.Error: raise except Exception as e: traceback.print_exc() raise gr.Error(f"Restoration failed: {e}") with gr.Blocks(title="Tape Restoration") as demo: gr.Markdown( """ # Analog Tape Restoration Upload audio digitized from a degraded analog master (soundtrack cue, film master, etc.) and restore it: de-click, de-hum, de-hiss, EQ rebuild, and gentle dynamics restoration. **Tip:** for the biggest quality gain, mark a 1–2 second region of pure tape hiss / room tone (no music) below — usually found at the very head or tail of the reel — so de-hiss learns *this tape's* actual noise floor instead of guessing. Long files run slowly on free CPU hardware; this demo caps input at 20 minutes. For longer reels, run `tape_restore.py` locally. """ ) with gr.Row(): with gr.Column(): audio_in = gr.Audio(label="Input audio", type="filepath") gr.Markdown("### Noise sample (optional but recommended)") use_noise_sample = gr.Checkbox( label="Use a noise-only region to profile hiss", value=False ) with gr.Row(): noise_start = gr.Number(label="Start (seconds)", value=0.0) noise_end = gr.Number(label="End (seconds)", value=2.0) gr.Markdown("### Stages") with gr.Row(): do_declick = gr.Checkbox(label="De-click / de-pop", value=True) do_dehum = gr.Checkbox(label="De-hum", value=True) with gr.Row(): do_dehiss = gr.Checkbox(label="De-hiss", value=True) do_eq = gr.Checkbox(label="EQ restoration", value=True) do_dynamics = gr.Checkbox(label="Dynamics restoration", value=True) gr.Markdown("### Parameters") hiss_strength = gr.Slider( 0.0, 1.0, value=0.75, step=0.05, label="De-hiss strength", info="Higher = more aggressive noise reduction, but can thin out highs/room tone", ) click_threshold = gr.Slider( 4.0, 40.0, value=15.0, step=1.0, label="Click sensitivity threshold", info="Lower = catches more clicks but risks false positives on transients", ) hum_freq = gr.Radio( choices=["60", "50"], value="60", label="Mains hum frequency", info="60 Hz = US/NA tapes, 50 Hz = EU/most of the rest of the world", ) wow_flutter = gr.Slider( 0.0, 0.5, value=0.0, step=0.05, label="Wow/flutter smoothing", info="Mild envelope-based warble reduction. 0 = off. For real pitch " "correction, use a dedicated tool like Capstan or iZotope RX.", ) run_btn = gr.Button("Restore", variant="primary") with gr.Column(): audio_out = gr.Audio(label="Restored audio", type="filepath") status = gr.Textbox(label="Status", interactive=False) run_btn.click( fn=run_restoration, inputs=[ audio_in, use_noise_sample, noise_start, noise_end, hiss_strength, click_threshold, hum_freq, wow_flutter, do_declick, do_dehum, do_dehiss, do_eq, do_dynamics, ], outputs=[audio_out, status], ) if __name__ == "__main__": demo.launch()