File size: 3,932 Bytes
139814d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
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()
|