Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,150 +4,80 @@ from transformers import pipeline
|
|
| 4 |
from moviepy import ImageClip, AudioFileClip
|
| 5 |
import os
|
| 6 |
|
| 7 |
-
# ---
|
| 8 |
-
# Music Genre Classifier (Public & Fast)
|
| 9 |
classifier = pipeline("audio-classification", model="dima806/music_genres_classification")
|
| 10 |
-
|
| 11 |
-
# External AI Spaces (API Clients)
|
| 12 |
mastering_client = Client("amaai-lab/SonicMaster")
|
| 13 |
image_client = Client("black-forest-labs/FLUX.1-schnell")
|
| 14 |
|
| 15 |
-
# --- 2. LOGIC FUNCTIONS ---
|
| 16 |
-
|
| 17 |
def master_logic(audio_path):
|
| 18 |
if not audio_path: return None, None, "Upload a track!", None
|
| 19 |
-
|
| 20 |
-
# 1. Detect Genre
|
| 21 |
results = classifier(audio_path)
|
| 22 |
genre = results[0]['label']
|
| 23 |
|
| 24 |
-
#
|
| 25 |
result = mastering_client.predict(
|
| 26 |
handle_file(audio_path),
|
| 27 |
f"Professional {genre} studio master.",
|
| 28 |
42, 25, 3.5
|
| 29 |
)
|
| 30 |
|
| 31 |
-
#
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
try:
|
| 35 |
-
# We split by "Saved: " and take the second part,
|
| 36 |
-
# then split by "\n" and take the first part.
|
| 37 |
-
clean_path = raw_output.split("Saved: ")[1].split("\n")[0].strip()
|
| 38 |
-
except Exception:
|
| 39 |
-
# Fallback just in case the API format is different
|
| 40 |
-
clean_path = raw_output
|
| 41 |
-
|
| 42 |
-
return clean_path, clean_path, f"Genre: {genre} | Mastered β¨", clean_path
|
| 43 |
|
| 44 |
def art_logic(status_text, vibe):
|
| 45 |
-
if not status_text or "Ready" in status_text:
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
# Clean the genre from the status message
|
| 49 |
-
clean_genre = str(status_text).split("**")[1] if "**" in str(status_text) else "Music"
|
| 50 |
-
prompt = f"Professional album cover art, {clean_genre} style, {vibe}, 4k, ultra-detailed, no text."
|
| 51 |
|
| 52 |
-
# Flux API call
|
| 53 |
img_result = image_client.predict(
|
| 54 |
-
prompt=
|
| 55 |
-
seed=0,
|
| 56 |
-
width=1024,
|
| 57 |
-
height=1024,
|
| 58 |
-
guidance_scale=3.5,
|
| 59 |
-
num_inference_steps=4,
|
| 60 |
api_name="/infer"
|
| 61 |
)
|
| 62 |
return img_result[0]
|
| 63 |
|
| 64 |
def video_logic(audio_path, image_path):
|
| 65 |
-
if not audio_path or not image_path:
|
| 66 |
-
return None
|
| 67 |
-
|
| 68 |
-
# MoviePy v2.0+ Syntax (Uses 'with_' and 'subclipped')
|
| 69 |
audio = AudioFileClip(audio_path).subclipped(0, 30)
|
| 70 |
img = ImageClip(image_path).with_duration(audio.duration).resized(width=1080)
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
video = video.with_audio(audio)
|
| 75 |
-
|
| 76 |
-
out_path = "promo_snippet.mp4"
|
| 77 |
-
video.write_videofile(out_path, fps=24, codec="libx264", audio_codec="aac")
|
| 78 |
-
return out_path
|
| 79 |
|
| 80 |
def toggle_ab(choice, raw, mastered):
|
| 81 |
return mastered if "Mastered" in choice else raw
|
| 82 |
|
| 83 |
-
# ---
|
| 84 |
-
with gr.Blocks(
|
| 85 |
-
gr.
|
| 86 |
-
|
| 87 |
-
# Persistent storage for audio paths
|
| 88 |
raw_storage = gr.State()
|
| 89 |
master_storage = gr.State()
|
| 90 |
|
| 91 |
with gr.Tabs():
|
| 92 |
-
|
| 93 |
-
|
|
|
|
| 94 |
with gr.Row():
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
with gr.Column():
|
| 112 |
-
vibe_in = gr.Textbox(label="Visual Vibe", placeholder="e.g. Cyberpunk Tokyo, oil painting...")
|
| 113 |
-
art_btn = gr.Button("π¨ GENERATE COVER")
|
| 114 |
-
art_out = gr.Image(label="Your Artwork", type="filepath")
|
| 115 |
-
|
| 116 |
-
# TAB 3: SOCIAL MEDIA EXPORT
|
| 117 |
-
with gr.TabItem("π± 3. Social Promo"):
|
| 118 |
-
promo_btn = gr.Button("π¬ CREATE TIKTOK SNIPPET", variant="primary")
|
| 119 |
-
video_out = gr.Video(label="Final 9:16 Video")
|
| 120 |
-
|
| 121 |
-
# --- 4. WIRING THE EVENTS ---
|
| 122 |
-
|
| 123 |
-
# Mastering logic
|
| 124 |
-
master_btn.click(
|
| 125 |
-
fn=master_logic,
|
| 126 |
-
inputs=in_audio,
|
| 127 |
-
outputs=[monitor, download_wav, status_display, master_storage]
|
| 128 |
-
).then(
|
| 129 |
-
fn=lambda x: x, inputs=in_audio, outputs=raw_storage
|
| 130 |
-
)
|
| 131 |
-
|
| 132 |
-
# A/B Toggle logic
|
| 133 |
-
ab_toggle.change(
|
| 134 |
-
fn=toggle_ab,
|
| 135 |
-
inputs=[ab_toggle, raw_storage, master_storage],
|
| 136 |
-
outputs=monitor
|
| 137 |
-
)
|
| 138 |
-
|
| 139 |
-
# Art logic (takes the genre from status + user vibe)
|
| 140 |
-
art_btn.click(
|
| 141 |
-
fn=art_logic,
|
| 142 |
-
inputs=[status_display, vibe_in],
|
| 143 |
-
outputs=art_out
|
| 144 |
-
)
|
| 145 |
-
|
| 146 |
-
# Video logic (takes mastered audio + generated art)
|
| 147 |
-
promo_btn.click(
|
| 148 |
-
fn=video_logic,
|
| 149 |
-
inputs=[master_storage, art_out],
|
| 150 |
-
outputs=video_out
|
| 151 |
)
|
|
|
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
|
|
|
|
|
|
| 4 |
from moviepy import ImageClip, AudioFileClip
|
| 5 |
import os
|
| 6 |
|
| 7 |
+
# --- ENGINES ---
|
|
|
|
| 8 |
classifier = pipeline("audio-classification", model="dima806/music_genres_classification")
|
|
|
|
|
|
|
| 9 |
mastering_client = Client("amaai-lab/SonicMaster")
|
| 10 |
image_client = Client("black-forest-labs/FLUX.1-schnell")
|
| 11 |
|
|
|
|
|
|
|
| 12 |
def master_logic(audio_path):
|
| 13 |
if not audio_path: return None, None, "Upload a track!", None
|
|
|
|
|
|
|
| 14 |
results = classifier(audio_path)
|
| 15 |
genre = results[0]['label']
|
| 16 |
|
| 17 |
+
# API call: returns [audio_file, status_text]
|
| 18 |
result = mastering_client.predict(
|
| 19 |
handle_file(audio_path),
|
| 20 |
f"Professional {genre} studio master.",
|
| 21 |
42, 25, 3.5
|
| 22 |
)
|
| 23 |
|
| 24 |
+
# USE result[0] for the actual file
|
| 25 |
+
mastered_file = result[0]
|
| 26 |
+
return mastered_file, mastered_file, f"Genre: **{genre}** | Mastered β¨", mastered_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
def art_logic(status_text, vibe):
|
| 29 |
+
if not status_text or "Ready" in status_text: return None
|
| 30 |
+
clean_genre = status_text.split("**")[1] if "**" in status_text else "Music"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
|
|
|
| 32 |
img_result = image_client.predict(
|
| 33 |
+
prompt=f"Professional album cover, {clean_genre}, {vibe}, high res",
|
| 34 |
+
seed=0, width=1024, height=1024, guidance_scale=3.5, num_inference_steps=4,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
api_name="/infer"
|
| 36 |
)
|
| 37 |
return img_result[0]
|
| 38 |
|
| 39 |
def video_logic(audio_path, image_path):
|
| 40 |
+
if not audio_path or not image_path: return None
|
|
|
|
|
|
|
|
|
|
| 41 |
audio = AudioFileClip(audio_path).subclipped(0, 30)
|
| 42 |
img = ImageClip(image_path).with_duration(audio.duration).resized(width=1080)
|
| 43 |
+
video = img.on_color(size=(1080, 1920), color=(15, 15, 15), pos="center").with_audio(audio)
|
| 44 |
+
video.write_videofile("promo.mp4", fps=24, codec="libx264")
|
| 45 |
+
return "promo.mp4"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
def toggle_ab(choice, raw, mastered):
|
| 48 |
return mastered if "Mastered" in choice else raw
|
| 49 |
|
| 50 |
+
# --- UI ---
|
| 51 |
+
with gr.Blocks() as demo:
|
| 52 |
+
gr.Markdown("# π΅ AI Artist Studio")
|
|
|
|
|
|
|
| 53 |
raw_storage = gr.State()
|
| 54 |
master_storage = gr.State()
|
| 55 |
|
| 56 |
with gr.Tabs():
|
| 57 |
+
with gr.TabItem("π§ 1. Master"):
|
| 58 |
+
in_audio = gr.Audio(label="Upload", type="filepath")
|
| 59 |
+
master_btn = gr.Button("π MASTER", variant="primary")
|
| 60 |
with gr.Row():
|
| 61 |
+
monitor = gr.Audio(label="Monitor")
|
| 62 |
+
ab_toggle = gr.Radio(["Original π", "Mastered β¨"], value="Mastered β¨", label="A/B")
|
| 63 |
+
status = gr.Markdown("Ready")
|
| 64 |
+
export_file = gr.File(label="WAV")
|
| 65 |
+
|
| 66 |
+
with gr.TabItem("π¨ 2. Art"):
|
| 67 |
+
vibe_in = gr.Textbox(label="Vibe")
|
| 68 |
+
art_btn = gr.Button("π¨ ART")
|
| 69 |
+
art_out = gr.Image()
|
| 70 |
+
|
| 71 |
+
with gr.TabItem("π± 3. Promo"):
|
| 72 |
+
promo_btn = gr.Button("π¬ VIDEO")
|
| 73 |
+
video_out = gr.Video()
|
| 74 |
+
|
| 75 |
+
master_btn.click(master_logic, in_audio, [monitor, export_file, status, master_storage]).then(
|
| 76 |
+
lambda x: x, in_audio, raw_storage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
)
|
| 78 |
+
ab_toggle.change(toggle_ab, [ab_toggle, raw_storage, master_storage], monitor)
|
| 79 |
+
art_btn.click(art_logic, [status, vibe_in], art_out)
|
| 80 |
+
promo_btn.click(video_logic, [master_storage, art_out], video_out)
|
| 81 |
|
| 82 |
+
# Theme moved to launch to fix the UserWarning
|
| 83 |
+
demo.launch(theme=gr.themes.Soft())
|