FLowHigh / app.py
harp-dev's picture
Keep the written file past process_fn and stop reaching for the output component
0d7d75f verified
Raw History Blame Contribute Delete
3.9 kB
import tempfile
from math import ceil
from pathlib import Path
import gradio as gr
import spaces
import torch
import torch.nn.functional as F
import torchaudio
from flowhigh import FlowHighSR
from huggingface_hub import snapshot_download
from pyharp import ModelCard, build_endpoint
from pyharp.tags import Subcategory
SAMPLE_RATE = 48000
checkpoint_dir = snapshot_download(
repo_id="ResembleAI/FlowHigh",
revision="4281fe4119e5f3d209a9a893218fed85af2e5bbe",
allow_patterns=["*.json", "*.pt"],
)
model = FlowHighSR.from_local(checkpoint_dir, device="cuda")
model_card = ModelCard(
name="FLowHigh",
description=(
"Extend the bandwidth of low-resolution audio with flow matching. "
"Repository: https://github.com/resemble-ai/flowhigh Paper: "
"https://arxiv.org/abs/2501.04926"
),
author="Jun-Hak Yun, Seung-Bin Kim, Seong-Whan Lee",
tags=[Subcategory.BANDWIDTH_EXTENSION],
)
@spaces.GPU
@torch.inference_mode()
def process_fn(input_audio_path: str, output_sample_rate: str) -> str:
if not input_audio_path:
raise gr.Error("Choose an audio file.")
audio, input_rate = torchaudio.load(input_audio_path)
audio = audio.mean(dim=0, keepdim=True)
if audio.shape[-1] == 0:
raise gr.Error("The audio file is empty.")
output_rate = int(output_sample_rate)
output_length = ceil(audio.shape[-1] * output_rate / input_rate)
native_length = ceil(audio.shape[-1] * SAMPLE_RATE / input_rate)
# Upstream divides by the input peak, so silence bypasses inference.
if audio.count_nonzero() == 0:
result = torch.zeros(1, output_length)
else:
minimum_length = ceil(2048 * input_rate / SAMPLE_RATE)
if audio.shape[-1] < minimum_length:
audio = F.pad(audio, (0, minimum_length - audio.shape[-1]))
result = model.generate(audio, input_rate, SAMPLE_RATE, timestep=1).cpu()
result = result[..., :native_length]
if output_rate != SAMPLE_RATE:
result = torchaudio.functional.resample(result, SAMPLE_RATE, output_rate)
result = result[..., :output_length]
peak = result.abs().max()
if peak > 1:
result *= 0.99 / peak
# mkdtemp, not a context manager: that would delete the directory on exit,
# taking the written file with it before the caller can read it, which is
# what the block-cache call here was working around. That also reached for
# the output component, which exists only under __main__ and so is
# undefined in the worker process that imports this module.
directory = Path(tempfile.mkdtemp(prefix="flowhigh-"))
output_path = directory / f"{directory.name}.wav"
torchaudio.save(str(output_path), result, output_rate)
return str(output_path)
if __name__ == "__main__":
with gr.Blocks(title="FLowHigh", delete_cache=(600, 3600)) as demo:
input_audio = (
gr.Audio(type="filepath", label="Input Audio")
.harp_required(True)
.set_info("Low-bandwidth audio to extend. Up to 30 seconds.")
.harp_required(True)
.set_info("Audio up to 30 seconds. Stereo inputs are mixed to mono.")
)
output_rate = gr.Dropdown(
choices=["24000", "48000", "96000"],
value="48000",
label="Output Sample Rate",
).set_info("The model runs at 48 kHz; 24 and 96 kHz outputs are resampled.")
output_audio = gr.Audio(type="filepath", label="Enhanced Audio").set_info(
"Mono WAV at the selected sample rate. Downloads are retained for one hour."
)
build_endpoint(
model_card=model_card,
input_components=[input_audio, output_rate],
output_components=[output_audio],
process_fn=process_fn,
)
demo.queue(default_concurrency_limit=1).launch(show_error=True, pwa=True)