NoteGPT / app.py
Avi3738's picture
Create app.py
139814d verified
import gradio as gr
import tempfile, os, subprocess, shutil
from typing import List, Tuple
from mutagen.id3 import ID3, ID3NoHeaderError, GEOB, TSSE
from mutagen.mp3 import MP3
TARGET_SR = 44100
TARGET_CH = 1
TARGET_BR = "128k"
def standardize_one(in_path: str, tsse_text: str, geob_bytes: bytes, geob_mime: str, geob_desc: str) -> str:
base = os.path.splitext(os.path.basename(in_path))[0]
out_path = os.path.join(tempfile.gettempdir(), f"{base}_standardized.mp3")
# 1) Re-encode audio with FFmpeg and enforce container/tag options
cmd = [
"ffmpeg", "-y",
"-i", in_path,
"-ar", str(TARGET_SR),
"-ac", str(TARGET_CH),
"-c:a", "libmp3lame",
"-b:a", TARGET_BR,
"-write_xing", "0",
"-id3v2_version", "4",
"-write_id3v1", "0",
"-metadata", f"TSSE={tsse_text or 'HF-Space'}",
out_path
]
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# 2) Ensure ID3v2.4 and add GEOB + TSSE with Mutagen
try:
tags = ID3(out_path)
except ID3NoHeaderError:
tags = ID3()
# Enforce TSSE (software/encoder)
tags.setall("TSSE", [TSSE(encoding=3, text=[tsse_text or "HF-Space"])])
# Always add a GEOB frame (either from upload or a tiny placeholder)
if geob_bytes is None or len(geob_bytes) == 0:
geob_bytes = b"HF-Space GEOB placeholder"
geob_mime = geob_mime or "application/octet-stream"
geob_desc = geob_desc or "asset"
tags.add(GEOB(encoding=3, mime=geob_mime or "application/octet-stream", desc=geob_desc or "asset", data=geob_bytes))
# Save as ID3v2.4 explicitly
tags.save(out_path, v2_version=4)
# Sanity check via Mutagen
_ = MP3(out_path) # will raise if corrupt
return out_path
def standardize_many(files: List[gr.File], tsse_text: str, geob_file: gr.File, geob_mime: str, geob_desc: str):
outputs = []
geob_bytes = None
if geob_file is not None and hasattr(geob_file, "name") and geob_file.name:
with open(geob_file.name, "rb") as f:
geob_bytes = f.read()
for f in files or []:
out = standardize_one(f.name, tsse_text, geob_bytes, geob_mime, geob_desc)
outputs.append(out)
# Optionally zip if multiple
if len(outputs) > 1:
zip_path = os.path.join(tempfile.gettempdir(), "standardized_mp3s.zip")
if os.path.exists(zip_path):
os.remove(zip_path)
with tempfile.TemporaryDirectory() as tmpd:
paths = []
for p in outputs:
dst = os.path.join(tmpd, os.path.basename(p))
shutil.copy2(p, dst)
paths.append(dst)
shutil.make_archive(zip_path[:-4], 'zip', tmpd)
return zip_path
elif len(outputs) == 1:
return outputs[0]
else:
return None
with gr.Blocks(title="MP3 Standardizer (44.1k/mono/128k CBR, no Xing, ID3v2.4, TSSE+GEOB)") as demo:
gr.Markdown("Upload MP3s to standardize them to 44.1 kHz, mono, 128 kbps CBR, no Xing/Info, ID3v2.4 only, plus TSSE and GEOB tags.")
with gr.Row():
files = gr.File(label="MP3 files", file_types=[".mp3"], file_count="multiple")
with gr.Accordion("Metadata options", open=False):
tsse = gr.Textbox(label="TSSE (encoder/software)", value="HF-Space", placeholder="e.g., Lavf60.16.100")
geob = gr.File(label="GEOB payload (optional)", file_types=[".bin", ".txt", ".json", ".png", ".jpg", ".mp3", ".mp4"], file_count="single")
geob_mime = gr.Textbox(label="GEOB MIME type", value="application/octet-stream")
geob_desc = gr.Textbox(label="GEOB description", value="asset")
run = gr.Button("Standardize")
result = gr.File(label="Download standardized file(s)")
run.click(standardize_many, inputs=[files, tsse, geob, geob_mime, geob_desc], outputs=[result])
if __name__ == "__main__":
demo.launch()