Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import json
|
3 |
+
from pydub import AudioSegment
|
4 |
+
from pydub.effects import normalize, compress_dynamic_range, low_pass_filter, high_pass_filter
|
5 |
+
import numpy as np
|
6 |
+
import gradio as gr
|
7 |
+
|
8 |
+
# Default presets for common genres
|
9 |
+
PRESETS = {
|
10 |
+
"rock": {"gain": 6, "compress_threshold": -20, "compress_ratio": 4, "low_pass": 12000, "high_pass": 80},
|
11 |
+
"pop": {"gain": 4, "compress_threshold": -18, "compress_ratio": 3, "low_pass": 15000, "high_pass": 100},
|
12 |
+
"jazz": {"gain": 3, "compress_threshold": -22, "compress_ratio": 2, "low_pass": 14000, "high_pass": 60},
|
13 |
+
"electronic": {"gain": 8, "compress_threshold": -16, "compress_ratio": 5, "low_pass": 20000, "high_pass": 120},
|
14 |
+
}
|
15 |
+
|
16 |
+
# JSON export extension
|
17 |
+
EXPORT_EXT = ".master"
|
18 |
+
|
19 |
+
# Custom CSS for Gradio
|
20 |
+
CUSTOM_CSS = """
|
21 |
+
body { background-color: #1e1e1e; color: #f0f0f0; }
|
22 |
+
.gradio-container { border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); }
|
23 |
+
#title { font-size: 2rem; margin-bottom: 1rem; }
|
24 |
+
"""
|
25 |
+
|
26 |
+
def apply_mastering(audio: AudioSegment, params: dict) -> AudioSegment:
|
27 |
+
# Gain
|
28 |
+
processed = audio + params["gain"]
|
29 |
+
# Compression
|
30 |
+
processed = compress_dynamic_range(processed,
|
31 |
+
threshold=params["compress_threshold"],
|
32 |
+
ratio=params["compress_ratio"])
|
33 |
+
# EQ filters
|
34 |
+
processed = high_pass_filter(processed, params["high_pass"])
|
35 |
+
processed = low_pass_filter(processed, params["low_pass"])
|
36 |
+
# Normalization
|
37 |
+
processed = normalize(processed)
|
38 |
+
return processed
|
39 |
+
|
40 |
+
|
41 |
+
def process_and_export(audio_file, preset_name, gain, threshold, ratio, lp, hp):
|
42 |
+
# Load
|
43 |
+
audio = AudioSegment.from_file(audio_file.name)
|
44 |
+
# Determine params
|
45 |
+
if preset_name != "custom":
|
46 |
+
params = PRESETS[preset_name]
|
47 |
+
else:
|
48 |
+
params = {"gain": gain, "compress_threshold": threshold, "compress_ratio": ratio,
|
49 |
+
"low_pass": lp, "high_pass": hp}
|
50 |
+
# Process
|
51 |
+
out = apply_mastering(audio, params)
|
52 |
+
# Export audio
|
53 |
+
buf = io.BytesIO()
|
54 |
+
out.export(buf, format="wav")
|
55 |
+
buf.seek(0)
|
56 |
+
# Create preset JSON
|
57 |
+
preset = {"preset": preset_name, "params": params}
|
58 |
+
preset_buf = io.BytesIO(json.dumps(preset, indent=2).encode())
|
59 |
+
preset_buf.name = "preset" + EXPORT_EXT
|
60 |
+
return (buf, "processed.wav"), (preset_buf, preset_buf.name)
|
61 |
+
|
62 |
+
|
63 |
+
def load_preset_json(file_obj):
|
64 |
+
data = json.load(file_obj)
|
65 |
+
name = data.get("preset", "custom")
|
66 |
+
params = data.get("params", {})
|
67 |
+
return name, params.get("gain", 0), params.get("compress_threshold", 0), \
|
68 |
+
params.get("compress_ratio", 1), params.get("low_pass", 20000), params.get("high_pass", 20)
|
69 |
+
|
70 |
+
with gr.Blocks(css=CUSTOM_CSS) as demo:
|
71 |
+
gr.Markdown("<div id='title'>Advanced Browser-based Audio Mastering Suite</div>")
|
72 |
+
with gr.Row():
|
73 |
+
with gr.Column():
|
74 |
+
audio_input = gr.Audio(label="Upload Audio", type="file")
|
75 |
+
preset_dropdown = gr.Dropdown(choices=list(PRESETS.keys()) + ["custom"], value="rock", label="Preset")
|
76 |
+
gain = gr.Slider(-10, 20, value=PRESETS["rock"]["gain"], label="Gain (dB)")
|
77 |
+
threshold = gr.Slider(-60, 0, value=PRESETS["rock"]["compress_threshold"], label="Compress Threshold (dB)")
|
78 |
+
ratio = gr.Slider(1, 10, value=PRESETS["rock"]["compress_ratio"], label="Compress Ratio")
|
79 |
+
lp = gr.Slider(1000, 20000, value=PRESETS["rock"]["low_pass"], label="Low-pass Frequency (Hz)")
|
80 |
+
hp = gr.Slider(20, 500, value=PRESETS["rock"]["high_pass"], label="High-pass Frequency (Hz)")
|
81 |
+
load_preset = gr.File(label="Load .master Preset", file_types=[".master"])
|
82 |
+
export_button = gr.Button("Process & Export")
|
83 |
+
with gr.Column():
|
84 |
+
output_audio = gr.Audio(label="Processed Audio")
|
85 |
+
export_preset_file = gr.File(label="Download Preset (.master)")
|
86 |
+
|
87 |
+
# Interactivity
|
88 |
+
def sync_sliders(preset):
|
89 |
+
if preset != "custom":
|
90 |
+
p = PRESETS[preset]
|
91 |
+
return gr.update(value=p["gain"]), gr.update(value=p["compress_threshold"]), \
|
92 |
+
gr.update(value=p["compress_ratio"]), gr.update(value=p["low_pass"]), \
|
93 |
+
gr.update(value=p["high_pass"])
|
94 |
+
return None
|
95 |
+
|
96 |
+
preset_dropdown.change(sync_sliders, inputs=[preset_dropdown], outputs=[gain, threshold, ratio, lp, hp])
|
97 |
+
load_preset.upload(load_preset_json, inputs=[load_preset],
|
98 |
+
outputs=[preset_dropdown, gain, threshold, ratio, lp, hp])
|
99 |
+
export_button.click(process_and_export,
|
100 |
+
inputs=[audio_input, preset_dropdown, gain, threshold, ratio, lp, hp],
|
101 |
+
outputs=[output_audio, export_preset_file])
|
102 |
+
|
103 |
+
if __name__ == "__main__":
|
104 |
+
demo.launch()
|