compare-codec / app.py
twangodev's picture
feat: add Reconstruct button and optimize codec processing logic
458a0e7 verified
"""compare-codec — upload audio, pick codecs, hear reconstructions side by side."""
from __future__ import annotations
import time
from pathlib import Path
import gradio as gr
import numpy as np
from compare_codec import CodecConfig, get_all
from compare_codec.spectrogram import generate as make_spectrogram
MAX_DURATION_S = 30.0
def _encode_decode_one(
audio_path: Path, codec_name: str, cfg: CodecConfig
) -> tuple[np.ndarray, int, float]:
"""Run one codec config and return (audio_array, sample_rate, elapsed_seconds)."""
codec = get_all()[codec_name]
sr = cfg.params.get("sample_rate", codec.sample_rate)
t0 = time.perf_counter()
audio_out = codec.encode_decode(audio_path, cfg)
elapsed = time.perf_counter() - t0
max_samples = int(MAX_DURATION_S * sr)
if len(audio_out) > max_samples:
audio_out = audio_out[:max_samples]
return audio_out, sr, elapsed
def build_ui() -> gr.Blocks:
codecs = get_all()
with gr.Blocks(title="compare-codec") as demo:
gr.Markdown("# compare-codec")
audio_in = gr.Audio(
sources=["upload", "microphone"],
type="filepath",
label="Input audio",
)
run_btn = gr.Button("Reconstruct", variant="primary")
tab_components: dict[str, dict] = {}
with gr.Tabs():
for codec_name, codec in codecs.items():
configs = codec.configs()
config_labels = [c.name for c in configs]
with gr.Tab(label=codec_name) as tab:
config_dd = gr.Dropdown(
choices=config_labels,
value=config_labels[0],
label="Configuration",
)
audio_out = gr.Audio(
label="Reconstructed",
type="numpy",
interactive=False,
)
stats_md = gr.Markdown(value="*Upload audio to compare.*")
spec_img = gr.Image(
label="Spectrogram",
type="filepath",
interactive=False,
)
tab_components[codec_name] = {
"tab": tab,
"config_dd": config_dd,
"audio_out": audio_out,
"stats_md": stats_md,
"spec_img": spec_img,
"configs": configs,
"config_labels": config_labels,
}
active_tab = gr.State(value=list(codecs.keys())[0])
ordered_names = list(codecs.keys())
all_outputs = []
for name in ordered_names:
c = tab_components[name]
all_outputs.extend([c["audio_out"], c["stats_md"], c["spec_img"]])
def process_active(audio_path: str | None, current_tab: str, *dropdown_values):
"""Reconstruct only the active tab's codec."""
if audio_path is None:
return [gr.update()] * len(all_outputs)
dd_map = dict(zip(ordered_names, dropdown_values))
comps = tab_components[current_tab]
cfg_label = dd_map[current_tab]
cfg = next(c for c in comps["configs"] if c.name == cfg_label)
audio_out, sr, elapsed = _encode_decode_one(
Path(audio_path), current_tab, cfg
)
spec_path = make_spectrogram(audio_out, sr)
stats_text = (
f"**{elapsed:.2f}s**  |  "
f"{sr / 1000:.0f} kHz  |  "
f"{cfg_label}"
)
flat = []
for name in ordered_names:
if name == current_tab:
flat.extend(
[
gr.update(value=(sr, audio_out)),
gr.update(value=stats_text),
gr.update(value=str(spec_path)),
]
)
else:
flat.extend([gr.update(), gr.update(), gr.update()])
return flat
all_dropdowns = [tab_components[n]["config_dd"] for n in ordered_names]
btn_event = run_btn.click(
fn=process_active,
inputs=[audio_in, active_tab] + all_dropdowns,
outputs=all_outputs,
)
for codec_name, comps in tab_components.items():
comps["tab"].select(
fn=lambda name=codec_name: name,
inputs=[],
outputs=[active_tab],
)
return demo
demo = build_ui()
if __name__ == "__main__":
demo.launch()