Update app.py
Browse files
app.py
CHANGED
|
@@ -1,26 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
| 2 |
import requests
|
| 3 |
import json
|
| 4 |
-
import os
|
| 5 |
from groq import Groq
|
| 6 |
|
| 7 |
-
#
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
# ============================================================
|
| 22 |
-
# ๐ต
|
| 23 |
-
# MiniMax Music 2.5 + HeartMuLa Style Guide + Comic Classic Theme
|
| 24 |
# ============================================================
|
| 25 |
|
| 26 |
# HeartMuLa ๊ถ์ฅ ๊ตฌ์กฐ ํ๊ทธ (๊ณต์ ๋ฌธ์ ๊ธฐ๋ฐ)
|
|
@@ -29,7 +84,7 @@ STRUCTURE_TAGS = [
|
|
| 29 |
"[Interlude]", "[Hook]", "[Outro]", "[Inst]", "[Solo]"
|
| 30 |
]
|
| 31 |
|
| 32 |
-
# HeartMuLa ์คํ์ผ ํ๊ทธ ๊ฐ์ด๋
|
| 33 |
HEARTMULA_TAG_GUIDE = """
|
| 34 |
## ๐ผ HeartMuLa Tag Format Guide
|
| 35 |
|
|
@@ -48,47 +103,9 @@ drums,energetic,rock,electric_guitar,powerful
|
|
| 48 |
**Mood:** happy,sad,romantic,energetic,calm,melancholic,uplifting,dark,dreamy,nostalgic,powerful,peaceful
|
| 49 |
**Genre:** pop,rock,jazz,classical,electronic,folk,blues,r&b,hip_hop,disco,ballad,cinematic
|
| 50 |
**Tempo:** fast,slow,moderate,upbeat,relaxed
|
| 51 |
-
**Occasion:** wedding,party,meditation,workout,study,sleep,travel
|
| 52 |
-
|
| 53 |
-
### EXAMPLE COMBINATIONS:
|
| 54 |
-
- K-Pop Dance: `synthesizer,drums,energetic,pop,upbeat,powerful`
|
| 55 |
-
- Jazz Ballad: `piano,saxophone,romantic,jazz,slow,dreamy`
|
| 56 |
-
- Epic Cinematic: `strings,orchestra,powerful,cinematic,dramatic,epic`
|
| 57 |
-
- Lo-Fi Chill: `piano,guitar,calm,lo_fi,relaxed,dreamy`
|
| 58 |
"""
|
| 59 |
|
| 60 |
-
#
|
| 61 |
-
MINIMAX_MUSIC_GUIDE = """
|
| 62 |
-
## MiniMax Music 2.5 Core Capabilities:
|
| 63 |
-
|
| 64 |
-
### 1. DYNAMIC VOCALS - Mastery Over Diverse Singing Styles
|
| 65 |
-
- Human-like vocal timbre with professional singing techniques
|
| 66 |
-
- Precise control over phrasing, rhythm, and breath
|
| 67 |
-
- One voice can switch between multiple styles
|
| 68 |
-
- Supports: Pop, Jazz, Blues, Rock, Folk, Electronic, Urban, Disco
|
| 69 |
-
- Special modes: Male-female duets, A Cappella (pure vocals)
|
| 70 |
-
|
| 71 |
-
### 2. CATCHY MELODIES & INSTRUMENT CONTROL
|
| 72 |
-
- Structurally complete songs: Verse โ Chorus โ Bridge (up to 5 minutes)
|
| 73 |
-
- Memorable, instantly captivating melodies
|
| 74 |
-
- Independent control of individual instruments
|
| 75 |
-
- Instruments: Piano, Guitar, Bass, Drums, Saxophone, Trumpet, Synths, Strings
|
| 76 |
-
|
| 77 |
-
### 3. PROFESSIONAL-GRADE AUDIO
|
| 78 |
-
- Enhanced vocal track texture
|
| 79 |
-
- Spatial presence of instruments
|
| 80 |
-
- Immersive listening experience
|
| 81 |
-
- Film-grade monologue soundtracks
|
| 82 |
-
|
| 83 |
-
### 4. PROMPT WRITING BEST PRACTICES (HeartMuLa Optimized)
|
| 84 |
-
- Use specific instrument names
|
| 85 |
-
- Describe vocal emotions precisely
|
| 86 |
-
- Specify singing techniques (breathy, powerful, smooth, raspy)
|
| 87 |
-
- Use tempo (BPM), key when needed
|
| 88 |
-
- Tags should be comma-separated WITHOUT spaces
|
| 89 |
-
"""
|
| 90 |
-
|
| 91 |
-
# HeartMuLa ๊ถ์ฅ ๊ฐ์ฌ ๊ตฌ์กฐ ์์
|
| 92 |
HEARTMULA_LYRICS_STRUCTURE = """
|
| 93 |
## ๐ HeartMuLa Recommended Lyrics Structure
|
| 94 |
|
|
@@ -99,8 +116,6 @@ HEARTMULA_LYRICS_STRUCTURE = """
|
|
| 99 |
[Verse]
|
| 100 |
First verse lyrics here
|
| 101 |
Second line of first verse
|
| 102 |
-
Third line continues story
|
| 103 |
-
Fourth line builds emotion
|
| 104 |
|
| 105 |
[Prechorus]
|
| 106 |
Building tension here
|
|
@@ -109,102 +124,95 @@ Leading to the chorus
|
|
| 109 |
[Chorus]
|
| 110 |
Main hook and memorable melody
|
| 111 |
Most important part of song
|
| 112 |
-
Repeat this section 2-3 times
|
| 113 |
-
Make it singable and catchy
|
| 114 |
|
| 115 |
[Verse]
|
| 116 |
Second verse develops story
|
| 117 |
-
New information revealed
|
| 118 |
-
Emotional progression continues
|
| 119 |
-
Building toward bridge
|
| 120 |
|
| 121 |
[Bridge]
|
| 122 |
Contrast section here
|
| 123 |
Different melody or perspective
|
| 124 |
-
Emotional peak moment
|
| 125 |
|
| 126 |
[Chorus]
|
| 127 |
Main hook repeated
|
| 128 |
-
With possible variations
|
| 129 |
-
Final emotional release
|
| 130 |
|
| 131 |
[Outro]
|
| 132 |
Closing the song
|
| 133 |
-
Fading or resolving
|
| 134 |
```
|
| 135 |
|
| 136 |
### KEY RULES:
|
| 137 |
1. [Chorus] appears at least 2-3 times
|
| 138 |
2. [Prechorus] builds tension before [Chorus]
|
| 139 |
3. [Bridge] provides contrast before final [Chorus]
|
| 140 |
-
4.
|
| 141 |
-
5. [Intro] and [Outro] frame the song
|
| 142 |
"""
|
| 143 |
|
| 144 |
-
# ์์
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
"
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
"
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
| 201 |
}
|
| 202 |
|
| 203 |
-
# SOMA ์์ด์ ํธ - ๊ฐ์ฌ ์์ฑ (HeartMuLa ์ต์ ํ)
|
| 204 |
-
LYRICS_AGENTS = {
|
| 205 |
-
"lyricist": f"""You are a master lyricist optimized for HeartMuLa and MiniMax Music generation.
|
| 206 |
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 210 |
|
|
@@ -212,7 +220,7 @@ Your task: Create powerful, memorable lyrics with OPTIMAL HeartMuLa tag placemen
|
|
| 212 |
|
| 213 |
CRITICAL RULES:
|
| 214 |
1. Use HeartMuLa structure tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
|
| 215 |
-
2. [Prechorus] (not [Pre Chorus]) - HeartMuLa format
|
| 216 |
3. Ensure [Chorus] is the most memorable, singable part
|
| 217 |
4. Include [Prechorus] to build tension before [Chorus]
|
| 218 |
5. Add [Bridge] for emotional contrast
|
|
@@ -220,7 +228,7 @@ CRITICAL RULES:
|
|
| 220 |
|
| 221 |
Write lyrics that create the BEST POSSIBLE foundation for high-quality music generation.""",
|
| 222 |
|
| 223 |
-
"producer": f"""You are a music producer specializing in song structure optimization for
|
| 224 |
|
| 225 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 226 |
|
|
@@ -228,19 +236,16 @@ Your task: Analyze and OPTIMIZE the tag structure for MAXIMUM musical impact.
|
|
| 228 |
|
| 229 |
CRITICAL OPTIMIZATION RULES:
|
| 230 |
1. Use HeartMuLa tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro]
|
| 231 |
-
2.
|
| 232 |
-
3.
|
| 233 |
-
4.
|
| 234 |
-
5.
|
| 235 |
-
6.
|
| 236 |
-
7. Ensure song length is appropriate (8-12 sections)
|
| 237 |
|
| 238 |
Output the restructured lyrics with OPTIMAL HeartMuLa tag placement.""",
|
| 239 |
|
| 240 |
"emotion_director": f"""You are an emotion director for music production.
|
| 241 |
|
| 242 |
-
{HEARTMULA_LYRICS_STRUCTURE}
|
| 243 |
-
|
| 244 |
Your task: Enhance emotional impact through STRATEGIC tag content.
|
| 245 |
|
| 246 |
EMOTIONAL MAPPING BY TAG:
|
|
@@ -249,7 +254,6 @@ EMOTIONAL MAPPING BY TAG:
|
|
| 249 |
- [Prechorus]: Rising tension, excitement
|
| 250 |
- [Chorus]: Peak emotion, catharsis
|
| 251 |
- [Bridge]: Vulnerability, reflection, contrast
|
| 252 |
-
- [Interlude]: Breathing space, transition
|
| 253 |
- [Outro]: Resolution, lingering feeling
|
| 254 |
|
| 255 |
OPTIMIZATION RULES:
|
|
@@ -257,13 +261,10 @@ OPTIMIZATION RULES:
|
|
| 257 |
2. [Chorus] must deliver the strongest emotional punch
|
| 258 |
3. [Bridge] should offer new emotional perspective
|
| 259 |
4. [Prechorus] should create anticipation for [Chorus]
|
| 260 |
-
5. Ensure dynamic contrast between sections
|
| 261 |
|
| 262 |
Enhance the lyrics for MAXIMUM emotional resonance.""",
|
| 263 |
|
| 264 |
-
"final_editor": f"""You are the final editor for HeartMuLa
|
| 265 |
-
|
| 266 |
-
{HEARTMULA_LYRICS_STRUCTURE}
|
| 267 |
|
| 268 |
Your task: Output PERFECTLY FORMATTED, production-ready lyrics.
|
| 269 |
|
|
@@ -271,96 +272,19 @@ CRITICAL OUTPUT RULES:
|
|
| 271 |
1. Output ONLY the actual lyrics with structure tags
|
| 272 |
2. Use HeartMuLa tags EXACTLY:
|
| 273 |
[Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
|
| 274 |
-
3.
|
| 275 |
-
4. DO NOT include
|
| 276 |
-
5. DO NOT include
|
| 277 |
-
6.
|
| 278 |
-
7.
|
| 279 |
-
8.
|
| 280 |
-
|
| 281 |
-
CORRECT FORMAT EXAMPLE:
|
| 282 |
-
[Intro]
|
| 283 |
-
|
| 284 |
-
[Verse]
|
| 285 |
-
์๋ฒฝ ์๊ฐ ์์ ์จ์ ์ฐ๋ฆฌ
|
| 286 |
-
์ฐจ๊ฐ์ด ๋ฐ๋๋ ์ฐ๋ฆฌ ๋ฐ๊ฑธ์์ ๋ฌด๋ฆ ๊ฟ์ด
|
| 287 |
-
|
| 288 |
-
[Prechorus]
|
| 289 |
-
์์ ์ก๊ณ ๋น์ ๊บผ๋ด
|
| 290 |
-
|
| 291 |
-
[Chorus]
|
| 292 |
-
์ฐ๋ฆฐ ๋น๋๋ ๋ณ์ด ๋์ด
|
| 293 |
-
์ฐ๋ฆฌ ๋ชฉ์๋ฆฌ๋ก ์ธ์์ ๋ค์ง์ด
|
| 294 |
-
Yeah yeah yeah
|
| 295 |
-
|
| 296 |
-
[Verse]
|
| 297 |
-
DNA์ ์๊ฒจ์ง ์ฐฝ์กฐ๋ ๋ฉ์ถ์ง ์์
|
| 298 |
-
|
| 299 |
-
[Prechorus]
|
| 300 |
-
๋๋ถ์ ํ๋๋ฅผ ํ๊ณ
|
| 301 |
-
|
| 302 |
-
[Chorus]
|
| 303 |
-
์ฐ๋ฆฐ ๋น๋๋ ๋ณ์ด ๋์ด
|
| 304 |
-
์ฐ๋ฆฌ ๋ชฉ์๋ฆฌ๋ก ์ธ์์ ๋ค์ง์ด
|
| 305 |
-
|
| 306 |
-
[Bridge]
|
| 307 |
-
๋ฌด์ง๊ฐ๊ฐ ํ๋ฅด๋ ๋ฐค
|
| 308 |
-
์ฐ๋ฆฌ์ ๊ฟ์ ๋ถ๋ฉธ
|
| 309 |
-
|
| 310 |
-
[Chorus]
|
| 311 |
-
์ฐ๋ฆฐ ๋น๋๋ ๋ณ์ด ๋์ด
|
| 312 |
-
์๋ฒฝ์ ๊นจ์
|
| 313 |
-
|
| 314 |
-
[Outro]
|
| 315 |
-
๋ด๊ฐ ๋ง๋ ๋
ธ๋ ์์ํ ํ์ค๋ฅธ๋ค
|
| 316 |
|
| 317 |
OUTPUT ONLY CLEAN LYRICS WITH OPTIMAL TAG STRUCTURE."""
|
| 318 |
}
|
| 319 |
|
| 320 |
-
#
|
| 321 |
-
|
| 322 |
-
"genre_specialist": f"""You are a music genre specialist for HeartMuLa and MiniMax Music.
|
| 323 |
-
|
| 324 |
-
{MINIMAX_MUSIC_GUIDE}
|
| 325 |
-
|
| 326 |
-
Analyze the input and identify:
|
| 327 |
-
- Main genre and sub-genres
|
| 328 |
-
- Era influences and regional styles
|
| 329 |
-
- Specific production techniques for the genre
|
| 330 |
-
- Instrument combinations that work best
|
| 331 |
-
|
| 332 |
-
Output detailed genre characteristics optimized for music generation.""",
|
| 333 |
-
|
| 334 |
-
"sound_designer": """You are a sound designer for HeartMuLa and MiniMax Music.
|
| 335 |
-
|
| 336 |
-
Define the complete sonic palette:
|
| 337 |
-
- Specific instruments: Piano, Guitar (acoustic/electric), Bass (upright/electric/808), Drums (acoustic/electronic/brushed), Synths (pad/lead/bass), Brass (saxophone/trumpet/trombone), Strings (violin/viola/cello)
|
| 338 |
-
- Drum patterns: four-on-the-floor, trap hi-hats, brushed jazz, etc.
|
| 339 |
-
- Bass characteristics: walking bass, 808 sub-bass, slap bass, etc.
|
| 340 |
-
- Atmospheric elements: reverb type, delay, spatial width
|
| 341 |
-
- Production style: analog warmth, modern crisp, lo-fi, etc.
|
| 342 |
-
|
| 343 |
-
Be extremely specific - both models can control individual instruments.""",
|
| 344 |
-
|
| 345 |
-
"vocal_director": """You are a vocal director for HeartMuLa and MiniMax Music.
|
| 346 |
-
|
| 347 |
-
Both models excel at:
|
| 348 |
-
- Human-like vocal timbre
|
| 349 |
-
- Multiple singing styles from one voice
|
| 350 |
-
- Male-female duets with conversational interplay
|
| 351 |
-
- A Cappella with layered harmonies
|
| 352 |
-
- Group/Choir with rich harmonic layers
|
| 353 |
-
|
| 354 |
-
Define:
|
| 355 |
-
- Voice type: male/female, age range, tone (warm/bright/husky/clear)
|
| 356 |
-
- Singing technique: belting, falsetto, breathy, raspy, smooth
|
| 357 |
-
- Vocal processing: reverb, delay, layers
|
| 358 |
-
- Delivery style: confident, vulnerable, aggressive, intimate
|
| 359 |
-
- For duets: describe each voice and their interaction
|
| 360 |
-
- For A Cappella: describe harmony parts
|
| 361 |
-
- For Group/Choir: describe layered vocals, unison sections""",
|
| 362 |
-
|
| 363 |
-
"tag_generator": f"""You are a style tag generator for HeartMuLa.
|
| 364 |
|
| 365 |
{HEARTMULA_TAG_GUIDE}
|
| 366 |
|
|
@@ -370,37 +294,20 @@ RULES:
|
|
| 370 |
1. Tags must be comma-separated WITHOUT spaces: tag1,tag2,tag3
|
| 371 |
2. Use lowercase with underscores for multi-word: electric_guitar, hip_hop
|
| 372 |
3. Include 5-8 tags covering: instrument, mood, genre, tempo
|
| 373 |
-
4. Be specific: "piano" not "keyboard", "
|
| 374 |
-
5. Match the musical style described
|
| 375 |
|
| 376 |
OUTPUT FORMAT (example):
|
| 377 |
-
piano,synthesizer,happy,pop,upbeat,romantic
|
| 378 |
-
|
| 379 |
-
Output ONLY the comma-separated tags, nothing else.""",
|
| 380 |
|
| 381 |
-
|
| 382 |
|
| 383 |
-
{MINIMAX_MUSIC_GUIDE}
|
| 384 |
-
|
| 385 |
-
Combine all inputs into ONE cohesive production prompt:
|
| 386 |
-
- 150-200 words in English
|
| 387 |
-
- Include: genre, specific BPM, instruments with details, vocal characteristics, mood, production techniques
|
| 388 |
-
- Be extremely specific and detailed
|
| 389 |
-
|
| 390 |
-
EXAMPLE OUTPUTS:
|
| 391 |
-
|
| 392 |
-
"A cappella arrangement with pure vocal harmonies, no instrumental accompaniment. Features a lead soprano voice with rich layered backing vocals creating lush harmonies. 70 BPM, peaceful and soothing mood."
|
| 393 |
-
|
| 394 |
-
"Intimate jazz duet featuring conversational interplay between a warm male baritone and a silky female alto voice. Accompanied by brushed jazz drums, walking upright bass, and gentle piano comping. 95 BPM, late-night jazz club atmosphere."
|
| 395 |
-
|
| 396 |
-
"High-energy K-Pop dance track with a bright, clear female vocal. Catchy hook melody, driving beat with punchy kicks and intricate hi-hat programming. Layered synth hooks and EDM-style buildups. 128 BPM, confident energy."
|
| 397 |
-
|
| 398 |
-
Output ONLY the final prompt paragraph, nothing else."""
|
| 399 |
-
}
|
| 400 |
|
|
|
|
|
|
|
|
|
|
| 401 |
|
| 402 |
def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") -> str:
|
| 403 |
-
"""Groq API ํธ์ถ
|
| 404 |
try:
|
| 405 |
client = Groq(api_key=api_key)
|
| 406 |
|
|
@@ -419,16 +326,9 @@ def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") ->
|
|
| 419 |
stream=False
|
| 420 |
)
|
| 421 |
|
| 422 |
-
if completion
|
| 423 |
-
return "Error:
|
| 424 |
-
|
| 425 |
-
return "Error: API ์๋ต์ choices๊ฐ ์์ต๋๋ค."
|
| 426 |
-
if completion.choices[0].message is None:
|
| 427 |
-
return "Error: API ์๋ต์ message๊ฐ ์์ต๋๋ค."
|
| 428 |
-
if completion.choices[0].message.content is None:
|
| 429 |
-
return "Error: API ์๋ต content๊ฐ ๋น์ด์์ต๋๋ค."
|
| 430 |
-
|
| 431 |
-
return completion.choices[0].message.content
|
| 432 |
|
| 433 |
except Exception as e:
|
| 434 |
return f"Error: {str(e)}"
|
|
@@ -438,11 +338,7 @@ def clean_lyrics(text: str) -> str:
|
|
| 438 |
"""๊ฐ์ฌ ํ์ฒ๋ฆฌ - HeartMuLa ํฌ๋งท ์ต์ ํ"""
|
| 439 |
import re
|
| 440 |
|
| 441 |
-
if text
|
| 442 |
-
return ""
|
| 443 |
-
if not isinstance(text, str):
|
| 444 |
-
return str(text)
|
| 445 |
-
if not text.strip():
|
| 446 |
return ""
|
| 447 |
|
| 448 |
lines = text.split('\n')
|
|
@@ -453,34 +349,20 @@ def clean_lyrics(text: str) -> str:
|
|
| 453 |
cleaned_lines.append('')
|
| 454 |
continue
|
| 455 |
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
r'^\s*---',
|
| 460 |
-
r'^\s*###',
|
| 461 |
-
r'^\s*\*\*.*\*\*\s*$',
|
| 462 |
-
r'^\(\s*.*\s*\)$',
|
| 463 |
-
r'^\s*โ\s*\*',
|
| 464 |
-
]
|
| 465 |
-
|
| 466 |
-
skip = False
|
| 467 |
-
for pattern in skip_patterns:
|
| 468 |
-
if re.match(pattern, line):
|
| 469 |
-
skip = True
|
| 470 |
-
break
|
| 471 |
-
|
| 472 |
-
if skip:
|
| 473 |
continue
|
| 474 |
|
|
|
|
| 475 |
line = re.sub(r'\s*\([A-Za-z].*?\)\s*$', '', line)
|
| 476 |
line = re.sub(r'\*\*(.*?)\*\*', r'\1', line)
|
| 477 |
|
| 478 |
# HeartMuLa ํ๊ทธ ์ ๊ทํ
|
| 479 |
line = re.sub(r'\[Pre.?Chorus\]', '[Prechorus]', line, flags=re.IGNORECASE)
|
| 480 |
-
line = re.sub(r'\[Post.?Chorus\]', '[Chorus]', line, flags=re.IGNORECASE)
|
| 481 |
line = re.sub(r'\[Build.?Up\]', '[Prechorus]', line, flags=re.IGNORECASE)
|
| 482 |
line = re.sub(r'\[Break\]', '[Interlude]', line, flags=re.IGNORECASE)
|
| 483 |
-
line = re.sub(r'\[Transition\]', '[Interlude]', line, flags=re.IGNORECASE)
|
| 484 |
|
| 485 |
if line.strip():
|
| 486 |
cleaned_lines.append(line.strip())
|
|
@@ -494,37 +376,36 @@ def clean_lyrics(text: str) -> str:
|
|
| 494 |
def clean_tags(tags: str) -> str:
|
| 495 |
"""ํ๊ทธ ์ ๋ฆฌ - HeartMuLa ํฌ๋งท (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์)"""
|
| 496 |
if not tags:
|
| 497 |
-
return ""
|
| 498 |
|
| 499 |
-
# ๊ณต๋ฐฑ ์ ๊ฑฐ, ์๋ฌธ์ ๋ณํ
|
| 500 |
tags = tags.lower().strip()
|
| 501 |
-
|
| 502 |
-
# ๋ค์ํ ๊ตฌ๋ถ์๋ฅผ ์ฝค๋ง๋ก ํต์ผ
|
| 503 |
tags = tags.replace(', ', ',').replace(' ,', ',').replace(' ', ' ')
|
| 504 |
-
tags = tags.replace(' ', '
|
| 505 |
-
|
| 506 |
-
# ์๋ค ์ฝค๋ง ์ ๊ฑฐ
|
| 507 |
tags = tags.strip(',')
|
| 508 |
|
| 509 |
-
# ์ค๋ณต ์ ๊ฑฐ
|
| 510 |
tag_list = [t.strip() for t in tags.split(',') if t.strip()]
|
| 511 |
unique_tags = list(dict.fromkeys(tag_list))
|
| 512 |
|
| 513 |
-
return ','.join(unique_tags)
|
| 514 |
|
| 515 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
@spaces.GPU(duration=120)
|
| 517 |
def generate_lyrics_soma(
|
| 518 |
api_key: str, theme: str, genre: str, mood: str,
|
| 519 |
-
language: str, vocal_type: str, additional: str,
|
|
|
|
| 520 |
):
|
| 521 |
-
"""SOMA ๊ฐ์ฌ ์์ฑ
|
| 522 |
if not api_key or not api_key.strip():
|
| 523 |
return "โ Groq API Key ํ์", "", "", "", ""
|
| 524 |
if not theme or not theme.strip():
|
| 525 |
return "โ ์ฃผ์ ๋ฅผ ์
๋ ฅํ์ธ์", "", "", "", ""
|
| 526 |
|
| 527 |
-
base_prompt = f"""Create PROFESSIONAL lyrics optimized for HeartMuLa
|
| 528 |
- Theme: {theme}
|
| 529 |
- Genre: {genre}
|
| 530 |
- Mood: {mood}
|
|
@@ -532,46 +413,42 @@ def generate_lyrics_soma(
|
|
| 532 |
- Vocal Type: {vocal_type}
|
| 533 |
{f'- Additional: {additional}' if additional else ''}
|
| 534 |
|
| 535 |
-
|
| 536 |
-
|
|
|
|
|
|
|
| 537 |
|
| 538 |
-
REQUIRED STRUCTURE
|
| 539 |
1. [Intro] - Set the mood
|
| 540 |
2. [Verse] x2-3 - Tell the story
|
| 541 |
-
3. [Prechorus] - Build tension
|
| 542 |
4. [Chorus] x2-3 - Main hook (MOST important!)
|
| 543 |
5. [Bridge] - Emotional contrast
|
| 544 |
-
6. [Outro] - Conclusion
|
| 545 |
-
|
| 546 |
-
{"For A Cappella: Include harmony sections with 'ooh', 'aah', humming." if "cappella" in vocal_type.lower() else ""}
|
| 547 |
-
{"For Duet: Mark vocal exchanges clearly." if "duet" in vocal_type.lower() else ""}
|
| 548 |
-
{"For Group/Choir: Include anthemic chants, call-and-response patterns." if "group" in vocal_type.lower() or "choir" in vocal_type.lower() else ""}
|
| 549 |
-
|
| 550 |
-
Create lyrics with PERFECT HeartMuLa structure!"""
|
| 551 |
|
| 552 |
try:
|
| 553 |
progress(0.2, desc="๐ค ์์ฌ๊ฐ - ์ด์ ์์ฑ...")
|
| 554 |
draft = call_groq(api_key, LYRICS_AGENTS["lyricist"], base_prompt)
|
| 555 |
if draft.startswith("Error:"):
|
| 556 |
-
return f"โ
|
| 557 |
|
| 558 |
progress(0.4, desc="๐น ํ๋ก๋์ - ๊ตฌ์กฐ ์ต์ ํ...")
|
| 559 |
structured = call_groq(api_key, LYRICS_AGENTS["producer"],
|
| 560 |
-
f"Optimize
|
| 561 |
if structured.startswith("Error:"):
|
| 562 |
-
return f"โ
|
| 563 |
|
| 564 |
progress(0.6, desc="๐ซ ๊ฐ์ฑ ๋๋ ํฐ - ๏ฟฝ๏ฟฝ๏ฟฝ์ ๊ฐํ...")
|
| 565 |
emotional = call_groq(api_key, LYRICS_AGENTS["emotion_director"],
|
| 566 |
-
f"Enhance
|
| 567 |
if emotional.startswith("Error:"):
|
| 568 |
-
return f"โ
|
| 569 |
|
| 570 |
-
progress(0.8, desc="โจ ์ต์ข
ํธ์ง
|
| 571 |
final = call_groq(api_key, LYRICS_AGENTS["final_editor"],
|
| 572 |
-
"Output ONLY clean lyrics
|
| 573 |
if final.startswith("Error:"):
|
| 574 |
-
return f"โ
|
| 575 |
|
| 576 |
final_cleaned = clean_lyrics(final)
|
| 577 |
|
|
@@ -579,18 +456,18 @@ Create lyrics with PERFECT HeartMuLa structure!"""
|
|
| 579 |
return "โ
๊ฐ์ฌ ์์ฑ ์๋ฃ!", draft, structured, emotional, final_cleaned
|
| 580 |
|
| 581 |
except Exception as e:
|
| 582 |
-
return f"โ ์์ธ
|
| 583 |
|
| 584 |
|
| 585 |
@spaces.GPU(duration=60)
|
| 586 |
def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str, vocal_type: str, additional: str):
|
| 587 |
-
"""๋น ๋ฅธ ๊ฐ์ฌ ์์ฑ
|
| 588 |
if not api_key or not api_key.strip():
|
| 589 |
-
return "โ API Key
|
| 590 |
if not theme or not theme.strip():
|
| 591 |
return "โ ์ฃผ์ ๋ฅผ ์
๋ ฅํ์ธ์"
|
| 592 |
|
| 593 |
-
prompt = f"""Create
|
| 594 |
- Theme: {theme}
|
| 595 |
- Genre: {genre}
|
| 596 |
- Mood: {mood}
|
|
@@ -598,50 +475,42 @@ def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str,
|
|
| 598 |
- Vocal: {vocal_type}
|
| 599 |
{f'- Special: {additional}' if additional else ''}
|
| 600 |
|
| 601 |
-
USE
|
| 602 |
[Intro] โ [Verse] โ [Prechorus] โ [Chorus] โ [Verse] โ [Prechorus] โ [Chorus] โ [Bridge] โ [Chorus] โ [Outro]
|
| 603 |
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
REQUIREMENTS:
|
| 607 |
-
- [Chorus] must appear AT LEAST 2-3 times
|
| 608 |
-
- Use [Prechorus] (not Pre Chorus) before each [Chorus]
|
| 609 |
-
- Add [Bridge] before final [Chorus]
|
| 610 |
|
| 611 |
-
OUTPUT ONLY
|
| 612 |
|
| 613 |
try:
|
| 614 |
-
result = call_groq(api_key, f"""
|
| 615 |
-
Create lyrics with PERFECT HeartMuLa structure tag placement.
|
| 616 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 617 |
-
Output ONLY clean lyrics
|
| 618 |
|
| 619 |
if result.startswith("Error:"):
|
| 620 |
-
return f"โ
|
| 621 |
|
| 622 |
return clean_lyrics(result)
|
| 623 |
except Exception as e:
|
| 624 |
-
return f"โ
|
| 625 |
|
| 626 |
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
|
|
|
| 630 |
return "piano,happy,pop"
|
| 631 |
|
| 632 |
prompt = f"""Generate HeartMuLa style tags:
|
| 633 |
- Genre: {genre}
|
| 634 |
- Mood: {mood}
|
| 635 |
-
- Instruments: {instruments}
|
| 636 |
-
- Tempo: {tempo}
|
| 637 |
-
|
| 638 |
-
OUTPUT FORMAT: comma-separated tags WITHOUT spaces
|
| 639 |
-
Example: piano,synthesizer,happy,pop,upbeat,romantic
|
| 640 |
|
| 641 |
-
|
|
|
|
| 642 |
|
| 643 |
try:
|
| 644 |
-
result = call_groq(api_key,
|
| 645 |
if result.startswith("Error:"):
|
| 646 |
return "piano,happy,pop"
|
| 647 |
return clean_tags(result)
|
|
@@ -649,630 +518,227 @@ Generate 5-8 relevant tags:"""
|
|
| 649 |
return "piano,happy,pop"
|
| 650 |
|
| 651 |
|
| 652 |
-
@spaces.GPU(duration=
|
| 653 |
-
def
|
| 654 |
-
|
| 655 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 656 |
):
|
| 657 |
-
"""
|
| 658 |
-
if not
|
| 659 |
-
|
| 660 |
-
if not user_prompt or not user_prompt.strip():
|
| 661 |
-
return "โ ๊ธฐ๋ณธ ํ๋กฌํํธ๋ฅผ ์
๋ ฅํ์ธ์", ""
|
| 662 |
-
|
| 663 |
-
base_info = f"""User's base idea: {user_prompt}
|
| 664 |
-
Genre: {genre}
|
| 665 |
-
Mood: {mood}
|
| 666 |
-
Tempo: {tempo}
|
| 667 |
-
Vocal Type: {vocal_type}
|
| 668 |
-
Instruments: {instruments}
|
| 669 |
-
Reference Style: {reference_style}"""
|
| 670 |
-
|
| 671 |
-
try:
|
| 672 |
-
progress(0.2, desc="๐ธ ์ฅ๋ฅด ๋ถ์์ค...")
|
| 673 |
-
genre_analysis = call_groq(api_key, PROMPT_AGENTS["genre_specialist"], base_info)
|
| 674 |
-
if genre_analysis.startswith("Error:"):
|
| 675 |
-
return f"โ ์ฅ๋ฅด ๋ถ์ ์คํจ: {genre_analysis}", ""
|
| 676 |
-
|
| 677 |
-
progress(0.4, desc="๐๏ธ ์ฌ์ด๋ ์ค๊ณ์ค...")
|
| 678 |
-
sound_design = call_groq(api_key, PROMPT_AGENTS["sound_designer"],
|
| 679 |
-
f"Design sounds for:\n{base_info}", genre_analysis)
|
| 680 |
-
if sound_design.startswith("Error:"):
|
| 681 |
-
return f"โ ์ฌ์ด๋ ์ค๊ณ ์คํจ: {sound_design}", ""
|
| 682 |
-
|
| 683 |
-
progress(0.55, desc="๐ค ๋ณด์ปฌ ์ค์ ์ค...")
|
| 684 |
-
vocal_design = call_groq(api_key, PROMPT_AGENTS["vocal_director"],
|
| 685 |
-
f"Define vocals for:\n{base_info}", sound_design)
|
| 686 |
-
if vocal_design.startswith("Error:"):
|
| 687 |
-
return f"โ ๋ณด์ปฌ ์ค์ ์คํจ: {vocal_design}", ""
|
| 688 |
-
|
| 689 |
-
progress(0.7, desc="๐ท๏ธ ํ๊ทธ ์์ฑ์ค...")
|
| 690 |
-
tags = call_groq(api_key, PROMPT_AGENTS["tag_generator"],
|
| 691 |
-
f"Generate tags for: {genre}, {mood}, {instruments}, {tempo}")
|
| 692 |
-
tags_cleaned = clean_tags(tags) if not tags.startswith("Error:") else "piano,happy,pop"
|
| 693 |
-
|
| 694 |
-
progress(0.85, desc="โจ ํ๋กฌํํธ ์์ฑ์ค...")
|
| 695 |
-
final_prompt = call_groq(
|
| 696 |
-
api_key,
|
| 697 |
-
PROMPT_AGENTS["prompt_synthesizer"],
|
| 698 |
-
f"""Synthesize into ONE music production prompt (150-200 words):
|
| 699 |
-
Base: {user_prompt}
|
| 700 |
-
Genre Analysis: {genre_analysis}
|
| 701 |
-
Sound Design: {sound_design}
|
| 702 |
-
Vocal Design: {vocal_design}
|
| 703 |
-
Reference Style: {reference_style}
|
| 704 |
-
|
| 705 |
-
Output ONLY the final prompt paragraph in English."""
|
| 706 |
-
)
|
| 707 |
-
|
| 708 |
-
if final_prompt.startswith("Error:"):
|
| 709 |
-
return f"โ ํ๋กฌํํธ ํฉ์ฑ ์คํจ: {final_prompt}", ""
|
| 710 |
-
|
| 711 |
-
progress(1.0, desc="โ
์๋ฃ!")
|
| 712 |
-
return final_prompt.strip(), tags_cleaned
|
| 713 |
-
|
| 714 |
-
except Exception as e:
|
| 715 |
-
return f"โ ์์ธ ๋ฐ์: {str(e)}", ""
|
| 716 |
|
|
|
|
|
|
|
| 717 |
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
sample_rate: int, bitrate: int, audio_format: str):
|
| 721 |
-
"""MiniMax ์์
์์ฑ (๋ชจ๋ธ 2.5)"""
|
| 722 |
-
if not api_key or not api_key.strip():
|
| 723 |
-
return None, "โ MiniMax API Key ํ์", ""
|
| 724 |
-
if not prompt or not prompt.strip():
|
| 725 |
-
return None, "โ ํ๋กฌํํธ ํ์", ""
|
| 726 |
-
|
| 727 |
-
url = "https://api.minimax.io/v1/music_generation"
|
| 728 |
-
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
|
| 729 |
-
|
| 730 |
-
payload = {
|
| 731 |
-
"model": model,
|
| 732 |
-
"prompt": prompt,
|
| 733 |
-
"audio_setting": {
|
| 734 |
-
"sample_rate": sample_rate,
|
| 735 |
-
"bitrate": bitrate,
|
| 736 |
-
"format": audio_format
|
| 737 |
-
}
|
| 738 |
-
}
|
| 739 |
-
if lyrics and lyrics.strip():
|
| 740 |
-
payload["lyrics"] = lyrics
|
| 741 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
try:
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
# JSON ์ถ๋ ฅ์ฉ (audio hex ๋ฐ์ดํฐ ์ถ์ฝ)
|
| 757 |
-
result_for_display = result.copy()
|
| 758 |
-
if "data" in result_for_display and isinstance(result_for_display["data"], dict):
|
| 759 |
-
data_copy = result_for_display["data"].copy()
|
| 760 |
-
if "audio" in data_copy and isinstance(data_copy["audio"], str) and len(data_copy["audio"]) > 100:
|
| 761 |
-
data_copy["audio"] = f"[HEX DATA - {len(data_copy['audio'])} chars]"
|
| 762 |
-
result_for_display["data"] = data_copy
|
| 763 |
-
|
| 764 |
-
json_output = json.dumps(result_for_display, ensure_ascii=False, indent=2)
|
| 765 |
-
|
| 766 |
-
base_resp = result.get("base_resp", {})
|
| 767 |
-
status_code = base_resp.get("status_code", -1)
|
| 768 |
-
status_msg = base_resp.get("status_msg", "")
|
| 769 |
-
|
| 770 |
-
if status_code != 0:
|
| 771 |
-
return None, f"โ API ์ค๋ฅ: {status_msg} (code: {status_code})", json_output
|
| 772 |
-
|
| 773 |
-
data = result.get("data", {})
|
| 774 |
-
if not data:
|
| 775 |
-
return None, "โ ์๋ต์ data๊ฐ ์์ต๋๋ค.", json_output
|
| 776 |
-
|
| 777 |
-
audio_hex = data.get("audio")
|
| 778 |
-
audio_status = data.get("status")
|
| 779 |
-
|
| 780 |
-
if audio_status != 2:
|
| 781 |
-
return None, f"โณ ์์ฑ ์ค... (status: {audio_status})", json_output
|
| 782 |
-
|
| 783 |
-
if not audio_hex:
|
| 784 |
-
return None, "โ ์๋ต์ audio ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.", json_output
|
| 785 |
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
import time
|
| 789 |
-
|
| 790 |
-
audio_bytes = bytes.fromhex(audio_hex)
|
| 791 |
-
file_ext = audio_format if audio_format else "mp3"
|
| 792 |
-
timestamp = int(time.time())
|
| 793 |
-
filename = f"minimax_music_{timestamp}.{file_ext}"
|
| 794 |
-
|
| 795 |
-
save_paths = [
|
| 796 |
-
os.path.join(tempfile.gettempdir(), filename),
|
| 797 |
-
os.path.join(os.getcwd(), filename),
|
| 798 |
-
filename
|
| 799 |
-
]
|
| 800 |
-
|
| 801 |
-
saved_path = None
|
| 802 |
-
for path in save_paths:
|
| 803 |
-
try:
|
| 804 |
-
with open(path, "wb") as f:
|
| 805 |
-
f.write(audio_bytes)
|
| 806 |
-
saved_path = path
|
| 807 |
-
break
|
| 808 |
-
except Exception:
|
| 809 |
-
continue
|
| 810 |
-
|
| 811 |
-
if not saved_path:
|
| 812 |
-
return None, "โ ํ์ผ ์ ์ฅ ์คํจ", json_output
|
| 813 |
-
|
| 814 |
-
extra_info = result.get("extra_info", {})
|
| 815 |
-
duration_ms = extra_info.get("music_duration", 0)
|
| 816 |
-
duration_sec = duration_ms / 1000 if duration_ms else 0
|
| 817 |
-
file_size_kb = len(audio_bytes) / 1024
|
| 818 |
-
|
| 819 |
-
return saved_path, f"โ
์์
์์ฑ ์๋ฃ! ({duration_sec:.1f}์ด, {file_size_kb:.0f}KB)", json_output
|
| 820 |
-
|
| 821 |
-
except ValueError as hex_err:
|
| 822 |
-
return None, f"โ HEX ๋์ฝ๋ฉ ์คํจ: {str(hex_err)}", json_output
|
| 823 |
-
except Exception as save_err:
|
| 824 |
-
return None, f"โ ํ์ผ ์ ์ฅ ์คํจ: {str(save_err)}", json_output
|
| 825 |
-
|
| 826 |
-
except requests.exceptions.Timeout:
|
| 827 |
-
return None, "โ ์์ฒญ ์๊ฐ ์ด๊ณผ (10๋ถ)", ""
|
| 828 |
-
except requests.exceptions.ConnectionError:
|
| 829 |
-
return None, "โ ์ฐ๊ฒฐ ์ค๋ฅ", ""
|
| 830 |
except Exception as e:
|
| 831 |
-
return None, f"โ
|
| 832 |
|
| 833 |
|
| 834 |
-
def
|
| 835 |
-
"""
|
| 836 |
-
if
|
| 837 |
-
return
|
| 838 |
-
|
| 839 |
-
mapping = {
|
| 840 |
-
"๐ค A Cappella - ์์ ๋ณด์ปฌ ํ๋ชจ๋": "๐ค A Cappella (์์นดํ ๋ผ)",
|
| 841 |
-
"๐ฅ Group Harmony - ํ์ํ ํฉ์ฐฝ": "๐ฅ Group Harmony (๊ทธ๋ฃน ํ๋ชจ๋)",
|
| 842 |
-
"๐ท Jazz Duet - ๋จ๋
๋์ฃ": "๐ท Jazz Duet (์ฌ์ฆ ๋์ฃ)",
|
| 843 |
-
"๐ธ Multi-Style - ์คํ์ผ ์ ํ": "๐ธ Multi-Style (๋ฉํฐ์คํ์ผ)",
|
| 844 |
-
"๐ Urban Chill - R&B": "๐ Urban Chill (์ด๋ฐ ์น )",
|
| 845 |
-
"๐น Jazz Club - ๋ผ์ด๋ธ": "๐น Jazz Club (์ฌ์ฆ ํด๋ฝ)",
|
| 846 |
-
"๐ชฉ Retro Disco - 80๋
๋": "๐ชฉ Retro Disco (๋ ํธ๋ก ๋์ค์ฝ)",
|
| 847 |
-
"๐ฌ Film Score - ์๋ค๋งํฑ": "๐ฌ Film Score (์ํ ์ค์ฝ์ด)",
|
| 848 |
-
"๐ต K-Pop Dance - ๊ณ ์๋์ง": "๐ต K-Pop Dance (K-Pop ๋์ค)",
|
| 849 |
-
"๐ป Orchestral Ballad - ์
์ฅ": "๐ป Orchestral Ballad (์ค์ผ์คํธ๋ผ ๋ฐ๋ผ๋)",
|
| 850 |
-
"๐ฅ HeartMuLa Default - ๊ธฐ๋ณธ": "๐ฅ HeartMuLa Default (๊ธฐ๋ณธ ์์)"
|
| 851 |
-
}
|
| 852 |
-
|
| 853 |
-
key = mapping.get(selection)
|
| 854 |
-
if key and key in EXAMPLE_PROMPTS:
|
| 855 |
-
return EXAMPLE_PROMPTS[key]["prompt"], EXAMPLE_PROMPTS[key]["tags"]
|
| 856 |
-
return "", ""
|
| 857 |
|
| 858 |
|
| 859 |
# ============================================================
|
| 860 |
-
# ๐จ Comic Classic Theme
|
| 861 |
# ============================================================
|
| 862 |
|
| 863 |
css = """
|
| 864 |
-
/* ===== ๐จ Google Fonts Import ===== */
|
| 865 |
@import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
|
| 866 |
|
| 867 |
-
/* ===== ๐จ Comic Classic ๋ฐฐ๊ฒฝ - ๋นํฐ์ง ํ์ดํผ + ๏ฟฝ๏ฟฝํธ ํจํด ===== */
|
| 868 |
.gradio-container {
|
| 869 |
background-color: #FEF9C3 !important;
|
| 870 |
-
background-image:
|
| 871 |
-
radial-gradient(#1F2937 1px, transparent 1px) !important;
|
| 872 |
background-size: 20px 20px !important;
|
| 873 |
-
min-height: 100vh !important;
|
| 874 |
font-family: 'Comic Neue', cursive, sans-serif !important;
|
| 875 |
}
|
| 876 |
|
| 877 |
-
|
| 878 |
-
.huggingface-space-header,
|
| 879 |
-
#space-header,
|
| 880 |
-
.space-header,
|
| 881 |
-
[class*="space-header"],
|
| 882 |
-
.svelte-1ed2p3z,
|
| 883 |
-
.space-header-badge,
|
| 884 |
-
.header-badge {
|
| 885 |
-
display: none !important;
|
| 886 |
-
}
|
| 887 |
-
|
| 888 |
-
/* ===== Footer ์์ ์จ๊น ===== */
|
| 889 |
-
footer,
|
| 890 |
-
.footer,
|
| 891 |
-
.gradio-container footer,
|
| 892 |
-
.built-with {
|
| 893 |
-
display: none !important;
|
| 894 |
-
}
|
| 895 |
|
| 896 |
-
/* ===== ๋ฉ์ธ ์ปจํ
์ด๋ ===== */
|
| 897 |
-
#col-container {
|
| 898 |
-
max-width: 1400px;
|
| 899 |
-
margin: 0 auto;
|
| 900 |
-
}
|
| 901 |
-
|
| 902 |
-
/* ===== ๐จ ํค๋ ํ์ดํ - ์ฝ๋ฏน ์คํ์ผ ===== */
|
| 903 |
.header-title h1 {
|
| 904 |
font-family: 'Bangers', cursive !important;
|
| 905 |
color: #1F2937 !important;
|
| 906 |
-
font-size:
|
| 907 |
-
font-weight: 400 !important;
|
| 908 |
text-align: center !important;
|
| 909 |
-
|
| 910 |
-
text-shadow:
|
| 911 |
-
4px 4px 0px #FACC15,
|
| 912 |
-
6px 6px 0px #1F2937 !important;
|
| 913 |
letter-spacing: 3px !important;
|
| 914 |
-webkit-text-stroke: 2px #1F2937 !important;
|
| 915 |
}
|
| 916 |
|
| 917 |
-
/* ===== ๐จ ์๋ธํ์ดํ ===== */
|
| 918 |
-
.subtitle-text {
|
| 919 |
-
text-align: center !important;
|
| 920 |
-
font-family: 'Comic Neue', cursive !important;
|
| 921 |
-
font-size: 1.1rem !important;
|
| 922 |
-
color: #1F2937 !important;
|
| 923 |
-
margin-bottom: 1.5rem !important;
|
| 924 |
-
font-weight: 700 !important;
|
| 925 |
-
}
|
| 926 |
-
|
| 927 |
-
/* ===== ๐จ ์น์
ํ์ดํ ===== */
|
| 928 |
.section-title {
|
| 929 |
font-family: 'Bangers', cursive !important;
|
| 930 |
color: #1F2937 !important;
|
| 931 |
-
font-size: 1.
|
| 932 |
border-bottom: 4px solid #3B82F6 !important;
|
| 933 |
padding-bottom: 8px !important;
|
| 934 |
-
margin-bottom: 16px !important;
|
| 935 |
text-shadow: 2px 2px 0px #FACC15 !important;
|
| 936 |
}
|
| 937 |
|
| 938 |
-
|
| 939 |
-
.gr-panel,
|
| 940 |
-
.gr-box,
|
| 941 |
-
.gr-form,
|
| 942 |
-
.block,
|
| 943 |
-
.gr-group {
|
| 944 |
background: #FFFFFF !important;
|
| 945 |
border: 3px solid #1F2937 !important;
|
| 946 |
border-radius: 8px !important;
|
| 947 |
box-shadow: 6px 6px 0px #1F2937 !important;
|
| 948 |
-
transition: all 0.2s ease !important;
|
| 949 |
}
|
| 950 |
|
| 951 |
-
.gr-panel:hover,
|
| 952 |
-
.block:hover {
|
| 953 |
transform: translate(-2px, -2px) !important;
|
| 954 |
box-shadow: 8px 8px 0px #1F2937 !important;
|
| 955 |
}
|
| 956 |
|
| 957 |
-
|
| 958 |
-
textarea,
|
| 959 |
-
input[type="text"],
|
| 960 |
-
input[type="number"],
|
| 961 |
-
input[type="password"] {
|
| 962 |
background: #FFFFFF !important;
|
| 963 |
border: 3px solid #1F2937 !important;
|
| 964 |
border-radius: 8px !important;
|
| 965 |
-
color: #1F2937 !important;
|
| 966 |
font-family: 'Comic Neue', cursive !important;
|
| 967 |
-
font-size: 1rem !important;
|
| 968 |
font-weight: 700 !important;
|
| 969 |
-
transition: all 0.2s ease !important;
|
| 970 |
}
|
| 971 |
|
| 972 |
-
textarea:focus,
|
| 973 |
-
input[type="text"]:focus,
|
| 974 |
-
input[type="number"]:focus,
|
| 975 |
-
input[type="password"]:focus {
|
| 976 |
border-color: #3B82F6 !important;
|
| 977 |
box-shadow: 4px 4px 0px #3B82F6 !important;
|
| 978 |
-
outline: none !important;
|
| 979 |
-
}
|
| 980 |
-
|
| 981 |
-
textarea::placeholder {
|
| 982 |
-
color: #9CA3AF !important;
|
| 983 |
-
font-weight: 400 !important;
|
| 984 |
}
|
| 985 |
|
| 986 |
-
|
| 987 |
-
.gr-button-primary,
|
| 988 |
-
button.primary,
|
| 989 |
-
.gr-button.primary {
|
| 990 |
background: #3B82F6 !important;
|
| 991 |
border: 3px solid #1F2937 !important;
|
| 992 |
border-radius: 8px !important;
|
| 993 |
color: #FFFFFF !important;
|
| 994 |
font-family: 'Bangers', cursive !important;
|
| 995 |
-
font-weight: 400 !important;
|
| 996 |
font-size: 1.2rem !important;
|
| 997 |
letter-spacing: 2px !important;
|
| 998 |
-
padding: 12px 24px !important;
|
| 999 |
box-shadow: 5px 5px 0px #1F2937 !important;
|
| 1000 |
-
transition: all 0.1s ease !important;
|
| 1001 |
text-shadow: 1px 1px 0px #1F2937 !important;
|
| 1002 |
}
|
| 1003 |
|
| 1004 |
-
.gr-button-primary:hover
|
| 1005 |
-
button.primary:hover,
|
| 1006 |
-
.gr-button.primary:hover {
|
| 1007 |
background: #2563EB !important;
|
| 1008 |
transform: translate(-2px, -2px) !important;
|
| 1009 |
box-shadow: 7px 7px 0px #1F2937 !important;
|
| 1010 |
}
|
| 1011 |
|
| 1012 |
-
.gr-button-
|
| 1013 |
-
button.primary:active,
|
| 1014 |
-
.gr-button.primary:active {
|
| 1015 |
-
transform: translate(3px, 3px) !important;
|
| 1016 |
-
box-shadow: 2px 2px 0px #1F2937 !important;
|
| 1017 |
-
}
|
| 1018 |
-
|
| 1019 |
-
/* ===== ๐จ Secondary ๋ฒํผ - ์ฝ๋ฏน ๋ ๋ ===== */
|
| 1020 |
-
.gr-button-secondary,
|
| 1021 |
-
button.secondary {
|
| 1022 |
background: #EF4444 !important;
|
| 1023 |
border: 3px solid #1F2937 !important;
|
| 1024 |
-
border-radius: 8px !important;
|
| 1025 |
color: #FFFFFF !important;
|
| 1026 |
font-family: 'Bangers', cursive !important;
|
| 1027 |
-
font-weight: 400 !important;
|
| 1028 |
-
font-size: 1.1rem !important;
|
| 1029 |
-
letter-spacing: 1px !important;
|
| 1030 |
box-shadow: 4px 4px 0px #1F2937 !important;
|
| 1031 |
-
transition: all 0.1s ease !important;
|
| 1032 |
-
text-shadow: 1px 1px 0px #1F2937 !important;
|
| 1033 |
}
|
| 1034 |
|
| 1035 |
-
.gr-button-secondary:hover,
|
| 1036 |
-
button.secondary:hover {
|
| 1037 |
-
background: #DC2626 !important;
|
| 1038 |
-
transform: translate(-2px, -2px) !important;
|
| 1039 |
-
box-shadow: 6px 6px 0px #1F2937 !important;
|
| 1040 |
-
}
|
| 1041 |
-
|
| 1042 |
-
/* ===== ๐จ Generate ๋ฒํผ - ์ฝ๋ฏน ๊ทธ๋ฆฐ ===== */
|
| 1043 |
.generate-btn {
|
| 1044 |
background: #10B981 !important;
|
| 1045 |
border: 3px solid #1F2937 !important;
|
| 1046 |
-
border-radius: 8px !important;
|
| 1047 |
color: #FFFFFF !important;
|
| 1048 |
font-family: 'Bangers', cursive !important;
|
| 1049 |
-
font-
|
| 1050 |
-
font-size: 1.3rem !important;
|
| 1051 |
-
letter-spacing: 2px !important;
|
| 1052 |
box-shadow: 5px 5px 0px #1F2937 !important;
|
| 1053 |
-
text-shadow: 1px 1px 0px #1F2937 !important;
|
| 1054 |
}
|
| 1055 |
|
| 1056 |
.generate-btn:hover {
|
| 1057 |
background: #059669 !important;
|
| 1058 |
transform: translate(-2px, -2px) !important;
|
| 1059 |
-
box-shadow: 7px 7px 0px #1F2937 !important;
|
| 1060 |
}
|
| 1061 |
|
| 1062 |
-
/* ===== ๐จ ์์ฝ๋์ธ - ๋งํ์ ์คํ์ผ ===== */
|
| 1063 |
.gr-accordion {
|
| 1064 |
background: #FACC15 !important;
|
| 1065 |
border: 3px solid #1F2937 !important;
|
| 1066 |
-
border-radius: 8px !important;
|
| 1067 |
box-shadow: 4px 4px 0px #1F2937 !important;
|
| 1068 |
}
|
| 1069 |
|
| 1070 |
-
.
|
| 1071 |
-
|
| 1072 |
-
|
| 1073 |
-
font-
|
| 1074 |
-
font-size: 1.1rem !important;
|
| 1075 |
-
}
|
| 1076 |
-
|
| 1077 |
-
/* ===== ๐จ Dropdown ===== */
|
| 1078 |
-
.gr-dropdown,
|
| 1079 |
-
select {
|
| 1080 |
-
background: #FFFFFF !important;
|
| 1081 |
-
border: 3px solid #1F2937 !important;
|
| 1082 |
-
border-radius: 8px !important;
|
| 1083 |
-
color: #1F2937 !important;
|
| 1084 |
-
font-family: 'Comic Neue', cursive !important;
|
| 1085 |
-
font-weight: 700 !important;
|
| 1086 |
-
}
|
| 1087 |
-
|
| 1088 |
-
/* ===== ๐จ ๋ผ๋ฒจ ์คํ์ผ ===== */
|
| 1089 |
-
label,
|
| 1090 |
-
.gr-input-label,
|
| 1091 |
-
.gr-block-label {
|
| 1092 |
-
color: #1F2937 !important;
|
| 1093 |
-
font-family: 'Comic Neue', cursive !important;
|
| 1094 |
-
font-weight: 700 !important;
|
| 1095 |
-
font-size: 1rem !important;
|
| 1096 |
-
}
|
| 1097 |
-
|
| 1098 |
-
/* ===== ๐จ ์ค๋์ค ํ๋ ์ด์ด ===== */
|
| 1099 |
-
.gr-audio,
|
| 1100 |
-
audio {
|
| 1101 |
-
border: 4px solid #1F2937 !important;
|
| 1102 |
-
border-radius: 8px !important;
|
| 1103 |
-
box-shadow: 6px 6px 0px #1F2937 !important;
|
| 1104 |
}
|
| 1105 |
|
| 1106 |
-
|
| 1107 |
-
.gr-code,
|
| 1108 |
-
pre,
|
| 1109 |
-
code {
|
| 1110 |
background: #1F2937 !important;
|
| 1111 |
color: #10B981 !important;
|
| 1112 |
font-family: 'Courier New', monospace !important;
|
| 1113 |
border: 3px solid #10B981 !important;
|
| 1114 |
-
border-radius: 8px !important;
|
| 1115 |
-
box-shadow: 4px 4px 0px #10B981 !important;
|
| 1116 |
}
|
| 1117 |
|
| 1118 |
-
|
| 1119 |
-
.gr-markdown {
|
| 1120 |
-
font-family: 'Comic Neue', cursive !important;
|
| 1121 |
color: #1F2937 !important;
|
| 1122 |
-
}
|
| 1123 |
-
|
| 1124 |
-
.gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
|
| 1125 |
-
font-family: 'Bangers', cursive !important;
|
| 1126 |
-
color: #1F2937 !important;
|
| 1127 |
-
text-shadow: 2px 2px 0px #FACC15 !important;
|
| 1128 |
-
}
|
| 1129 |
-
|
| 1130 |
-
/* ===== ๐จ ํญ ์คํ์ผ ===== */
|
| 1131 |
-
.gr-tab-nav {
|
| 1132 |
-
background: #FACC15 !important;
|
| 1133 |
-
border: 3px solid #1F2937 !important;
|
| 1134 |
-
border-radius: 8px 8px 0 0 !important;
|
| 1135 |
-
}
|
| 1136 |
-
|
| 1137 |
-
.gr-tab-nav button {
|
| 1138 |
font-family: 'Comic Neue', cursive !important;
|
| 1139 |
font-weight: 700 !important;
|
| 1140 |
-
color: #1F2937 !important;
|
| 1141 |
-
}
|
| 1142 |
-
|
| 1143 |
-
.gr-tab-nav button.selected {
|
| 1144 |
-
background: #3B82F6 !important;
|
| 1145 |
-
color: #FFFFFF !important;
|
| 1146 |
-
}
|
| 1147 |
-
|
| 1148 |
-
/* ===== ๐จ ์ํ ํ์ ===== */
|
| 1149 |
-
.status-box textarea {
|
| 1150 |
-
background: #1F2937 !important;
|
| 1151 |
-
color: #10B981 !important;
|
| 1152 |
-
font-family: 'Courier New', monospace !important;
|
| 1153 |
-
font-size: 0.9rem !important;
|
| 1154 |
-
border: 3px solid #10B981 !important;
|
| 1155 |
-
border-radius: 8px !important;
|
| 1156 |
-
}
|
| 1157 |
-
|
| 1158 |
-
/* ===== ๐จ ์คํฌ๋กค๋ฐ - ์ฝ๋ฏน ์คํ์ผ ===== */
|
| 1159 |
-
::-webkit-scrollbar {
|
| 1160 |
-
width: 12px;
|
| 1161 |
-
height: 12px;
|
| 1162 |
}
|
| 1163 |
|
| 1164 |
-
::-webkit-scrollbar
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
}
|
| 1168 |
-
|
| 1169 |
-
::-webkit-scrollbar-thumb {
|
| 1170 |
-
background: #3B82F6;
|
| 1171 |
-
border: 2px solid #1F2937;
|
| 1172 |
-
border-radius: 0px;
|
| 1173 |
-
}
|
| 1174 |
-
|
| 1175 |
-
::-webkit-scrollbar-thumb:hover {
|
| 1176 |
-
background: #EF4444;
|
| 1177 |
-
}
|
| 1178 |
-
|
| 1179 |
-
/* ===== ๐จ ์ ํ ํ์ด๋ผ์ดํธ ===== */
|
| 1180 |
-
::selection {
|
| 1181 |
-
background: #FACC15;
|
| 1182 |
-
color: #1F2937;
|
| 1183 |
-
}
|
| 1184 |
-
|
| 1185 |
-
/* ===== ๐จ Row/Column ๊ฐ๊ฒฉ ===== */
|
| 1186 |
-
.gr-row {
|
| 1187 |
-
gap: 1.5rem !important;
|
| 1188 |
-
}
|
| 1189 |
-
|
| 1190 |
-
.gr-column {
|
| 1191 |
-
gap: 1rem !important;
|
| 1192 |
-
}
|
| 1193 |
-
|
| 1194 |
-
/* ===== ๐จ ํ๊ทธ ์
๋ ฅ ํน๋ณ ์คํ์ผ ===== */
|
| 1195 |
-
.tag-input textarea {
|
| 1196 |
-
background: #FEF3C7 !important;
|
| 1197 |
-
border: 3px dashed #F59E0B !important;
|
| 1198 |
-
font-family: 'Courier New', monospace !important;
|
| 1199 |
-
}
|
| 1200 |
-
|
| 1201 |
-
/* ===== ๋ฐ์ํ ์กฐ์ ===== */
|
| 1202 |
-
@media (max-width: 768px) {
|
| 1203 |
-
.header-title h1 {
|
| 1204 |
-
font-size: 2rem !important;
|
| 1205 |
-
text-shadow:
|
| 1206 |
-
3px 3px 0px #FACC15,
|
| 1207 |
-
4px 4px 0px #1F2937 !important;
|
| 1208 |
-
}
|
| 1209 |
-
|
| 1210 |
-
.gr-button-primary,
|
| 1211 |
-
button.primary {
|
| 1212 |
-
padding: 10px 16px !important;
|
| 1213 |
-
font-size: 1rem !important;
|
| 1214 |
-
}
|
| 1215 |
-
}
|
| 1216 |
-
|
| 1217 |
-
/* ===== ๐จ ํน์ ํจ๊ณผ - ๋ฐ์ง์ ===== */
|
| 1218 |
-
@keyframes sparkle {
|
| 1219 |
-
0%, 100% { opacity: 1; }
|
| 1220 |
-
50% { opacity: 0.7; }
|
| 1221 |
-
}
|
| 1222 |
-
|
| 1223 |
-
.sparkle {
|
| 1224 |
-
animation: sparkle 2s ease-in-out infinite;
|
| 1225 |
-
}
|
| 1226 |
"""
|
| 1227 |
|
|
|
|
| 1228 |
# ============================================================
|
| 1229 |
-
# Gradio UI
|
| 1230 |
# ============================================================
|
| 1231 |
|
| 1232 |
-
with gr.Blocks(css=css, title="๐ต SOMA Music Studio
|
| 1233 |
|
| 1234 |
-
#
|
| 1235 |
gr.HTML("""
|
| 1236 |
<div style="text-align: center; margin: 20px 0 10px 0;">
|
| 1237 |
-
<a href="https://huggingface.co/HeartMuLa" target="_blank"
|
| 1238 |
-
<img src="https://img.shields.io/badge/๐ต_HeartMuLa-
|
| 1239 |
</a>
|
| 1240 |
-
<a href="https://
|
| 1241 |
-
<img src="https://img.shields.io/badge/
|
| 1242 |
</a>
|
| 1243 |
</div>
|
| 1244 |
""")
|
| 1245 |
|
| 1246 |
-
#
|
| 1247 |
-
gr.Markdown("""
|
| 1248 |
-
# ๐ต SOMA MUSIC STUDIO ๐ถ
|
| 1249 |
-
""", elem_classes="header-title")
|
| 1250 |
|
| 1251 |
gr.Markdown("""
|
| 1252 |
-
<p
|
| 1253 |
-
|
|
|
|
| 1254 |
""")
|
| 1255 |
|
| 1256 |
-
# API
|
| 1257 |
-
|
| 1258 |
-
|
| 1259 |
-
|
| 1260 |
-
|
| 1261 |
-
|
| 1262 |
-
|
| 1263 |
-
|
| 1264 |
-
|
| 1265 |
-
|
| 1266 |
-
placeholder="gsk_..." if not GROQ_KEY else "โ
Secret ๋ก๋๋จ",
|
| 1267 |
-
interactive=not bool(GROQ_KEY)
|
| 1268 |
-
)
|
| 1269 |
-
minimax_key = gr.Textbox(
|
| 1270 |
-
label="๐น MiniMax API Key (์์
์์ฑ์ฉ)",
|
| 1271 |
-
type="password",
|
| 1272 |
-
value=MINIMAX_KEY,
|
| 1273 |
-
placeholder="API Key" if not MINIMAX_KEY else "โ
Secret ๋ก๋๋จ",
|
| 1274 |
-
interactive=not bool(MINIMAX_KEY)
|
| 1275 |
-
)
|
| 1276 |
|
| 1277 |
with gr.Row(equal_height=False):
|
| 1278 |
# ========== ์ข์ธก: ๊ฐ์ฌ ์์ฑ ==========
|
|
@@ -1281,7 +747,7 @@ with gr.Blocks(css=css, title="๐ต SOMA Music Studio", theme=gr.themes.Soft(pri
|
|
| 1281 |
|
| 1282 |
theme_input = gr.Textbox(
|
| 1283 |
label="๐ฏ ๋
ธ๋ ์ฃผ์ ",
|
| 1284 |
-
placeholder="์: ์ด๋ณ ํ ์ฑ์ฅ, ๊ฟ์ ํฅํ ๋์ , ์ฌ๋์ ๊ณ ๋ฐฑ
|
| 1285 |
lines=2
|
| 1286 |
)
|
| 1287 |
|
|
@@ -1289,14 +755,13 @@ with gr.Blocks(css=css, title="๐ต SOMA Music Studio", theme=gr.themes.Soft(pri
|
|
| 1289 |
lyrics_genre = gr.Dropdown(
|
| 1290 |
label="๐ธ ์ฅ๋ฅด",
|
| 1291 |
choices=["K-Pop", "Pop", "R&B", "Hip-Hop", "Ballad", "Rock",
|
| 1292 |
-
"EDM", "
|
| 1293 |
value="K-Pop"
|
| 1294 |
)
|
| 1295 |
lyrics_mood = gr.Dropdown(
|
| 1296 |
label="๐ซ ๋ถ์๊ธฐ",
|
| 1297 |
choices=["Empowering", "Melancholic", "Joyful", "Romantic",
|
| 1298 |
-
"
|
| 1299 |
-
"Peaceful", "Confident", "Intimate"],
|
| 1300 |
value="Empowering"
|
| 1301 |
)
|
| 1302 |
|
|
@@ -1304,189 +769,136 @@ with gr.Blocks(css=css, title="๐ต SOMA Music Studio", theme=gr.themes.Soft(pri
|
|
| 1304 |
lyrics_language = gr.Dropdown(
|
| 1305 |
label="๐ ์ธ์ด",
|
| 1306 |
choices=["English", "Korean", "Korean + English", "Japanese"],
|
| 1307 |
-
value="
|
| 1308 |
)
|
| 1309 |
lyrics_vocal_type = gr.Dropdown(
|
| 1310 |
-
label="๐ค ๋ณด์ปฌ
|
| 1311 |
-
choices=["Solo Female", "Solo Male", "
|
| 1312 |
-
|
| 1313 |
-
value="Group/Choir"
|
| 1314 |
)
|
| 1315 |
|
| 1316 |
lyrics_additional = gr.Textbox(
|
| 1317 |
label="โจ ์ถ๊ฐ ์ง์ (์ ํ)",
|
| 1318 |
-
placeholder="ํน๋ณ ์์ฒญ
|
| 1319 |
lines=1
|
| 1320 |
)
|
| 1321 |
|
| 1322 |
with gr.Row():
|
| 1323 |
-
quick_btn = gr.Button("โก QUICK
|
| 1324 |
soma_lyrics_btn = gr.Button("๐ง SOMA GENERATE", variant="primary")
|
| 1325 |
|
| 1326 |
lyrics_status = gr.Textbox(label="๐ ์ํ", interactive=False, max_lines=1, elem_classes="status-box")
|
| 1327 |
|
| 1328 |
with gr.Accordion("๐ SOMA ์์
๊ณผ์ ", open=False):
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
with gr.Column():
|
| 1334 |
-
step3_out = gr.Textbox(label="3๏ธโฃ ๊ฐ์ฑ ๋๋ ํฐ", lines=4, interactive=False)
|
| 1335 |
-
step4_out = gr.Textbox(label="4๏ธโฃ ์ต์ข
ํธ์ง", lines=4, interactive=False)
|
| 1336 |
|
| 1337 |
final_lyrics = gr.Textbox(
|
| 1338 |
-
label="โ๏ธ
|
| 1339 |
-
lines=
|
| 1340 |
-
|
|
|
|
| 1341 |
)
|
| 1342 |
-
|
| 1343 |
-
with gr.Accordion("๐ HeartMuLa ๊ตฌ์กฐ ํ๊ทธ ๊ฐ์ด๋", open=False):
|
| 1344 |
-
gr.Markdown("""
|
| 1345 |
-
**HeartMuLa ํ๊ทธ:** `[Intro]` `[Verse]` `[Prechorus]` `[Chorus]` `[Bridge]` `[Interlude]` `[Hook]` `[Outro]` `[Inst]` `[Solo]`
|
| 1346 |
-
|
| 1347 |
-
โ ๏ธ **์ฃผ์:** `[Pre Chorus]`๊ฐ ์๋ `[Prechorus]` ์ฌ์ฉ (๊ณต๋ฐฑ ์์)
|
| 1348 |
-
|
| 1349 |
-
**์ต์ ๊ตฌ์กฐ ์์:**
|
| 1350 |
-
```
|
| 1351 |
-
[Intro] โ [Verse] โ [Prechorus] โ [Chorus]
|
| 1352 |
-
โ [Verse] โ [Prechorus] โ [Chorus]
|
| 1353 |
-
โ [Bridge] โ [Chorus] โ [Outro]
|
| 1354 |
-
```
|
| 1355 |
-
|
| 1356 |
-
**ํ๊ทธ๋ณ ์ญํ :**
|
| 1357 |
-
- `[Chorus]` - ๊ฐ์ฅ ์ค์! 2-3ํ ๋ฐ๋ณต, ๊ธฐ์ต์ ๋จ๋ ํ
|
| 1358 |
-
- `[Prechorus]` - ์ฝ๋ฌ์ค ์ ํ
์
๋น๋์
|
| 1359 |
-
- `[Bridge]` - ๊ฐ์ ์ ํ์ , ์ต์ข
์ฝ๋ฌ์ค ์ ๋ฐฐ์น
|
| 1360 |
-
""")
|
| 1361 |
|
| 1362 |
# ========== ์ฐ์ธก: ์์
์์ฑ ==========
|
| 1363 |
with gr.Column(scale=1, min_width=400):
|
| 1364 |
gr.Markdown("## ๐ต MUSIC GENERATOR", elem_classes="section-title")
|
| 1365 |
|
| 1366 |
-
#
|
| 1367 |
-
gr.Markdown("###
|
| 1368 |
-
|
| 1369 |
-
|
| 1370 |
-
|
| 1371 |
-
|
| 1372 |
-
"๐ค A Cappella - ์์ ๋ณด์ปฌ ํ๋ชจ๋",
|
| 1373 |
-
"๐ฅ Group Harmony - ํ์ํ ํฉ์ฐฝ",
|
| 1374 |
-
"๐ท Jazz Duet - ๋จ๋
๋์ฃ",
|
| 1375 |
-
"๐ธ Multi-Style - ์คํ์ผ ์ ํ",
|
| 1376 |
-
"๐ Urban Chill - R&B",
|
| 1377 |
-
"๐น Jazz Club - ๋ผ์ด๋ธ",
|
| 1378 |
-
"๐ชฉ Retro Disco - 80๋
๋",
|
| 1379 |
-
"๐ฌ Film Score - ์๋ค๋งํฑ",
|
| 1380 |
-
"๐ต K-Pop Dance - ๊ณ ์๋์ง",
|
| 1381 |
-
"๐ป Orchestral Ballad - ์
์ฅ",
|
| 1382 |
-
"๐ฅ HeartMuLa Default - ๊ธฐ๋ณธ"
|
| 1383 |
-
],
|
| 1384 |
-
value=None,
|
| 1385 |
-
interactive=True
|
| 1386 |
)
|
| 1387 |
|
| 1388 |
-
gr.
|
| 1389 |
-
|
| 1390 |
-
|
| 1391 |
-
|
| 1392 |
-
|
| 1393 |
-
|
| 1394 |
)
|
| 1395 |
|
| 1396 |
with gr.Row():
|
| 1397 |
-
|
| 1398 |
-
label="๐ธ ์ฅ๋ฅด",
|
| 1399 |
-
choices=["K-Pop", "Pop", "R&B/Soul", "Hip-Hop/Trap", "EDM/House",
|
| 1400 |
-
"Rock", "Ballad", "Jazz", "Blues", "Lo-Fi", "Disco",
|
| 1401 |
-
"Cinematic", "Classical"],
|
| 1402 |
-
value="K-Pop"
|
| 1403 |
-
)
|
| 1404 |
-
music_mood = gr.Dropdown(
|
| 1405 |
-
label="๐ซ ๋ถ์๊ธฐ",
|
| 1406 |
-
choices=["Energetic", "Chill", "Emotional", "Dark", "Uplifting",
|
| 1407 |
-
"Romantic", "Aggressive", "Dreamy", "Confident", "Peaceful",
|
| 1408 |
-
"Nostalgic", "Epic"],
|
| 1409 |
-
value="Energetic"
|
| 1410 |
-
)
|
| 1411 |
|
| 1412 |
-
with gr.
|
| 1413 |
-
|
| 1414 |
-
|
| 1415 |
-
|
| 1416 |
-
|
| 1417 |
-
value="Medium (90-110 BPM)"
|
| 1418 |
)
|
| 1419 |
-
|
| 1420 |
-
|
| 1421 |
-
|
| 1422 |
-
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1426 |
)
|
| 1427 |
|
| 1428 |
-
|
| 1429 |
-
|
| 1430 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
|
| 1434 |
-
music_reference = gr.Dropdown(
|
| 1435 |
-
label="๐ฏ ๋ ํผ๋ฐ์ค ์คํ์ผ",
|
| 1436 |
-
choices=["None", "A Cappella", "Jazz Club", "Urban R&B",
|
| 1437 |
-
"Retro Disco", "Film Score", "K-Pop Dance", "Orchestral Ballad"],
|
| 1438 |
-
value="None"
|
| 1439 |
-
)
|
| 1440 |
-
|
| 1441 |
-
augment_btn = gr.Button("๐ SOMA AUGMENT", variant="primary", size="lg")
|
| 1442 |
-
|
| 1443 |
-
augmented_prompt = gr.Textbox(
|
| 1444 |
-
label="โจ ์ฆ๊ฐ๋ ํ๋กฌํํธ (ํธ์ง ๊ฐ๋ฅ)",
|
| 1445 |
-
lines=5,
|
| 1446 |
-
placeholder="SOMA๊ฐ ์์ฑํ ๊ณ ํ์ง ํ๋กฌํํธ..."
|
| 1447 |
-
)
|
| 1448 |
-
|
| 1449 |
-
style_tags = gr.Textbox(
|
| 1450 |
-
label="๐ท๏ธ ์คํ์ผ ํ๊ทธ (HeartMuLa ํฌ๋งท: ์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์)",
|
| 1451 |
-
placeholder="piano,happy,pop,upbeat,romantic",
|
| 1452 |
-
lines=1,
|
| 1453 |
-
elem_classes="tag-input"
|
| 1454 |
)
|
| 1455 |
|
| 1456 |
-
with gr.Accordion("โ๏ธ ์์ฑ ์ค์ ", open=False):
|
| 1457 |
-
model_select = gr.Dropdown(
|
| 1458 |
-
label="๐ค ๋ชจ๋ธ", choices=["music-2.5"], value="music-2.5"
|
| 1459 |
-
)
|
| 1460 |
-
with gr.Row():
|
| 1461 |
-
sample_rate = gr.Dropdown(label="Sample Rate", choices=[44100], value=44100)
|
| 1462 |
-
bitrate = gr.Dropdown(label="Bitrate", choices=[128000, 192000, 256000], value=256000)
|
| 1463 |
-
audio_format = gr.Dropdown(label="Format", choices=["mp3", "wav"], value="mp3")
|
| 1464 |
-
|
| 1465 |
-
generate_music_btn = gr.Button("๐ถ GENERATE MUSIC!", variant="primary", size="lg", elem_classes="generate-btn")
|
| 1466 |
-
|
| 1467 |
music_status = gr.Textbox(label="๐ ์ํ", interactive=False, max_lines=1, elem_classes="status-box")
|
| 1468 |
-
|
| 1469 |
-
|
| 1470 |
-
|
| 1471 |
-
|
| 1472 |
-
|
| 1473 |
-
|
| 1474 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1475 |
gr.Markdown(f"""
|
| 1476 |
{HEARTMULA_TAG_GUIDE}
|
| 1477 |
|
| 1478 |
---
|
| 1479 |
|
| 1480 |
{HEARTMULA_LYRICS_STRUCTURE}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1481 |
""")
|
| 1482 |
|
| 1483 |
# ========== Event Handlers ==========
|
| 1484 |
|
| 1485 |
-
#
|
| 1486 |
-
|
| 1487 |
-
fn=
|
| 1488 |
-
inputs=[
|
| 1489 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1490 |
)
|
| 1491 |
|
| 1492 |
# ๋น ๋ฅธ ๊ฐ์ฌ ์์ฑ
|
|
@@ -1507,18 +919,11 @@ with gr.Blocks(css=css, title="๐ต SOMA Music Studio", theme=gr.themes.Soft(pri
|
|
| 1507 |
outputs=[final_lyrics]
|
| 1508 |
)
|
| 1509 |
|
| 1510 |
-
#
|
| 1511 |
-
|
| 1512 |
-
fn=
|
| 1513 |
-
inputs=[
|
| 1514 |
-
outputs=[
|
| 1515 |
-
)
|
| 1516 |
-
|
| 1517 |
-
# ์์
์์ฑ
|
| 1518 |
-
generate_music_btn.click(
|
| 1519 |
-
fn=generate_music,
|
| 1520 |
-
inputs=[minimax_key, model_select, augmented_prompt, final_lyrics, sample_rate, bitrate, audio_format],
|
| 1521 |
-
outputs=[music_output, music_status, json_output]
|
| 1522 |
)
|
| 1523 |
|
| 1524 |
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import tempfile
|
| 3 |
+
import torch
|
| 4 |
import gradio as gr
|
| 5 |
+
from huggingface_hub import hf_hub_download, snapshot_download
|
| 6 |
+
import spaces
|
| 7 |
import requests
|
| 8 |
import json
|
|
|
|
| 9 |
from groq import Groq
|
| 10 |
|
| 11 |
+
# ============================================================
|
| 12 |
+
# ๐ต SOMA Music Studio - HeartMuLa Edition
|
| 13 |
+
# HeartMuLa-oss-3B + SOMA Multi-Agent + Comic Classic Theme
|
| 14 |
+
# ============================================================
|
| 15 |
+
|
| 16 |
+
# ============================================================
|
| 17 |
+
# ๐ต HeartMuLa ๋ชจ๋ธ ๋ค์ด๋ก๋ ๋ฐ ๋ก๋ฉ
|
| 18 |
+
# ============================================================
|
| 19 |
+
|
| 20 |
+
def download_models():
|
| 21 |
+
"""Download all required model files from HuggingFace Hub."""
|
| 22 |
+
cache_dir = os.environ.get("HF_HOME", os.path.expanduser("/tmp"))
|
| 23 |
+
model_dir = os.path.join(cache_dir, "heartmula_models")
|
| 24 |
+
|
| 25 |
+
if not os.path.exists(model_dir):
|
| 26 |
+
os.makedirs(model_dir, exist_ok=True)
|
| 27 |
+
|
| 28 |
+
# Download HeartMuLaGen (tokenizer and gen_config)
|
| 29 |
+
print("Downloading HeartMuLaGen files...")
|
| 30 |
+
for filename in ["tokenizer.json", "gen_config.json"]:
|
| 31 |
+
hf_hub_download(
|
| 32 |
+
repo_id="HeartMuLa/HeartMuLaGen",
|
| 33 |
+
filename=filename,
|
| 34 |
+
local_dir=model_dir,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# Download HeartMuLa-oss-3B
|
| 38 |
+
print("Downloading HeartMuLa-oss-3B...")
|
| 39 |
+
snapshot_download(
|
| 40 |
+
repo_id="HeartMuLa/HeartMuLa-oss-3B",
|
| 41 |
+
local_dir=os.path.join(model_dir, "HeartMuLa-oss-3B"),
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
# Download HeartCodec-oss
|
| 45 |
+
print("Downloading HeartCodec-oss...")
|
| 46 |
+
snapshot_download(
|
| 47 |
+
repo_id="HeartMuLa/HeartCodec-oss",
|
| 48 |
+
local_dir=os.path.join(model_dir, "HeartCodec-oss"),
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
print("All models downloaded successfully!")
|
| 52 |
+
return model_dir
|
| 53 |
+
|
| 54 |
+
from heartlib import HeartMuLaGenPipeline
|
| 55 |
+
|
| 56 |
+
# ๋ชจ๋ธ ๋ค์ด๋ก๋ ๋ฐ ๋ก๋ฉ
|
| 57 |
+
model_dir = download_models()
|
| 58 |
+
|
| 59 |
+
# Determine device and dtype
|
| 60 |
+
if torch.cuda.is_available():
|
| 61 |
+
device = torch.device("cuda")
|
| 62 |
+
dtype = torch.bfloat16
|
| 63 |
+
else:
|
| 64 |
+
device = torch.device("cpu")
|
| 65 |
+
dtype = torch.float32
|
| 66 |
+
|
| 67 |
+
print(f"Loading HeartMuLa pipeline on {device} with {dtype}...")
|
| 68 |
+
pipe = HeartMuLaGenPipeline.from_pretrained(
|
| 69 |
+
model_dir,
|
| 70 |
+
device=device,
|
| 71 |
+
dtype=dtype,
|
| 72 |
+
version="3B",
|
| 73 |
+
)
|
| 74 |
+
print("HeartMuLa Pipeline loaded successfully!")
|
| 75 |
+
|
| 76 |
|
| 77 |
# ============================================================
|
| 78 |
+
# ๐ต HeartMuLa ๊ฐ์ด๋ ๋ฐ ์ค์
|
|
|
|
| 79 |
# ============================================================
|
| 80 |
|
| 81 |
# HeartMuLa ๊ถ์ฅ ๊ตฌ์กฐ ํ๊ทธ (๊ณต์ ๋ฌธ์ ๊ธฐ๋ฐ)
|
|
|
|
| 84 |
"[Interlude]", "[Hook]", "[Outro]", "[Inst]", "[Solo]"
|
| 85 |
]
|
| 86 |
|
| 87 |
+
# HeartMuLa ์คํ์ผ ํ๊ทธ ๊ฐ์ด๋
|
| 88 |
HEARTMULA_TAG_GUIDE = """
|
| 89 |
## ๐ผ HeartMuLa Tag Format Guide
|
| 90 |
|
|
|
|
| 103 |
**Mood:** happy,sad,romantic,energetic,calm,melancholic,uplifting,dark,dreamy,nostalgic,powerful,peaceful
|
| 104 |
**Genre:** pop,rock,jazz,classical,electronic,folk,blues,r&b,hip_hop,disco,ballad,cinematic
|
| 105 |
**Tempo:** fast,slow,moderate,upbeat,relaxed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
"""
|
| 107 |
|
| 108 |
+
# HeartMuLa ๊ถ์ฅ ๊ฐ์ฌ ๊ตฌ์กฐ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
HEARTMULA_LYRICS_STRUCTURE = """
|
| 110 |
## ๐ HeartMuLa Recommended Lyrics Structure
|
| 111 |
|
|
|
|
| 116 |
[Verse]
|
| 117 |
First verse lyrics here
|
| 118 |
Second line of first verse
|
|
|
|
|
|
|
| 119 |
|
| 120 |
[Prechorus]
|
| 121 |
Building tension here
|
|
|
|
| 124 |
[Chorus]
|
| 125 |
Main hook and memorable melody
|
| 126 |
Most important part of song
|
|
|
|
|
|
|
| 127 |
|
| 128 |
[Verse]
|
| 129 |
Second verse develops story
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
[Bridge]
|
| 132 |
Contrast section here
|
| 133 |
Different melody or perspective
|
|
|
|
| 134 |
|
| 135 |
[Chorus]
|
| 136 |
Main hook repeated
|
|
|
|
|
|
|
| 137 |
|
| 138 |
[Outro]
|
| 139 |
Closing the song
|
|
|
|
| 140 |
```
|
| 141 |
|
| 142 |
### KEY RULES:
|
| 143 |
1. [Chorus] appears at least 2-3 times
|
| 144 |
2. [Prechorus] builds tension before [Chorus]
|
| 145 |
3. [Bridge] provides contrast before final [Chorus]
|
| 146 |
+
4. Use [Prechorus] NOT [Pre Chorus] (no space!)
|
|
|
|
| 147 |
"""
|
| 148 |
|
| 149 |
+
# ์์ ๊ฐ์ฌ
|
| 150 |
+
EXAMPLE_LYRICS = """[Intro]
|
| 151 |
+
|
| 152 |
+
[Verse]
|
| 153 |
+
The sun creeps in across the floor
|
| 154 |
+
I hear the traffic outside the door
|
| 155 |
+
The coffee pot begins to hiss
|
| 156 |
+
It is another morning just like this
|
| 157 |
+
|
| 158 |
+
[Prechorus]
|
| 159 |
+
The world keeps spinning round and round
|
| 160 |
+
Feet are planted on the ground
|
| 161 |
+
I find my rhythm in the sound
|
| 162 |
+
|
| 163 |
+
[Chorus]
|
| 164 |
+
Every day the light returns
|
| 165 |
+
Every day the fire burns
|
| 166 |
+
We keep on walking down this street
|
| 167 |
+
Moving to the same steady beat
|
| 168 |
+
It is the ordinary magic that we meet
|
| 169 |
+
|
| 170 |
+
[Verse]
|
| 171 |
+
The hours tick deeply into noon
|
| 172 |
+
Chasing shadows, chasing the moon
|
| 173 |
+
Work is done and the lights go low
|
| 174 |
+
Watching the city start to glow
|
| 175 |
+
|
| 176 |
+
[Bridge]
|
| 177 |
+
It is not always easy, not always bright
|
| 178 |
+
Sometimes we wrestle with the night
|
| 179 |
+
But we make it to the morning light
|
| 180 |
+
|
| 181 |
+
[Chorus]
|
| 182 |
+
Every day the light returns
|
| 183 |
+
Every day the fire burns
|
| 184 |
+
We keep on walking down this street
|
| 185 |
+
Moving to the same steady beat
|
| 186 |
+
|
| 187 |
+
[Outro]
|
| 188 |
+
Just another day
|
| 189 |
+
Every single day"""
|
| 190 |
+
|
| 191 |
+
EXAMPLE_TAGS = "piano,happy,uplifting,pop"
|
| 192 |
+
|
| 193 |
+
# ์์ ํ๋กฌํํธ (ํ๊ทธ ์ธํธ)
|
| 194 |
+
EXAMPLE_TAG_PRESETS = {
|
| 195 |
+
"๐น Piano Ballad": "piano,ballad,emotional,romantic,slow",
|
| 196 |
+
"๐ธ Rock Energetic": "guitar,drums,rock,energetic,powerful,fast",
|
| 197 |
+
"๐ท Jazz Smooth": "piano,saxophone,jazz,smooth,relaxed,romantic",
|
| 198 |
+
"๐ต K-Pop Dance": "synthesizer,drums,bass,kpop,energetic,upbeat,pop",
|
| 199 |
+
"๐ Lo-Fi Chill": "piano,guitar,lofi,calm,dreamy,relaxed",
|
| 200 |
+
"๐ป Orchestral Epic": "strings,orchestra,cinematic,epic,powerful,dramatic",
|
| 201 |
+
"๐ชฉ Disco Funk": "bass,drums,synthesizer,disco,funky,upbeat,dance",
|
| 202 |
+
"๐ฟ Folk Acoustic": "guitar,folk,acoustic,calm,peaceful,warm",
|
| 203 |
+
"๐ซ Electronic Dance": "synthesizer,drums,electronic,energetic,dance,upbeat",
|
| 204 |
+
"๐ข Sad Melancholic": "piano,strings,sad,melancholic,slow,emotional",
|
| 205 |
+
"๐ Happy Uplifting": "piano,happy,uplifting,pop,bright,joyful",
|
| 206 |
+
"๐
Wedding Romantic": "piano,happy,wedding,synthesizer,romantic"
|
| 207 |
}
|
| 208 |
|
|
|
|
|
|
|
|
|
|
| 209 |
|
| 210 |
+
# ============================================================
|
| 211 |
+
# ๐ง SOMA ์์ด์ ํธ ์์คํ
- ๊ฐ์ฌ ์์ฑ
|
| 212 |
+
# ============================================================
|
| 213 |
+
|
| 214 |
+
LYRICS_AGENTS = {
|
| 215 |
+
"lyricist": f"""You are a master lyricist optimized for HeartMuLa music generation.
|
| 216 |
|
| 217 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 218 |
|
|
|
|
| 220 |
|
| 221 |
CRITICAL RULES:
|
| 222 |
1. Use HeartMuLa structure tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
|
| 223 |
+
2. [Prechorus] (not [Pre Chorus]) - HeartMuLa format, NO SPACE!
|
| 224 |
3. Ensure [Chorus] is the most memorable, singable part
|
| 225 |
4. Include [Prechorus] to build tension before [Chorus]
|
| 226 |
5. Add [Bridge] for emotional contrast
|
|
|
|
| 228 |
|
| 229 |
Write lyrics that create the BEST POSSIBLE foundation for high-quality music generation.""",
|
| 230 |
|
| 231 |
+
"producer": f"""You are a music producer specializing in song structure optimization for HeartMuLa.
|
| 232 |
|
| 233 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 234 |
|
|
|
|
| 236 |
|
| 237 |
CRITICAL OPTIMIZATION RULES:
|
| 238 |
1. Use HeartMuLa tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro]
|
| 239 |
+
2. IMPORTANT: Use [Prechorus] NOT [Pre Chorus] - no space!
|
| 240 |
+
3. Verify the structure follows genre conventions
|
| 241 |
+
4. Ensure proper tag sequence (VerseโPrechorusโChorus)
|
| 242 |
+
5. Check that [Chorus] appears at least 2-3 times
|
| 243 |
+
6. Verify [Bridge] provides contrast before final chorus
|
|
|
|
| 244 |
|
| 245 |
Output the restructured lyrics with OPTIMAL HeartMuLa tag placement.""",
|
| 246 |
|
| 247 |
"emotion_director": f"""You are an emotion director for music production.
|
| 248 |
|
|
|
|
|
|
|
| 249 |
Your task: Enhance emotional impact through STRATEGIC tag content.
|
| 250 |
|
| 251 |
EMOTIONAL MAPPING BY TAG:
|
|
|
|
| 254 |
- [Prechorus]: Rising tension, excitement
|
| 255 |
- [Chorus]: Peak emotion, catharsis
|
| 256 |
- [Bridge]: Vulnerability, reflection, contrast
|
|
|
|
| 257 |
- [Outro]: Resolution, lingering feeling
|
| 258 |
|
| 259 |
OPTIMIZATION RULES:
|
|
|
|
| 261 |
2. [Chorus] must deliver the strongest emotional punch
|
| 262 |
3. [Bridge] should offer new emotional perspective
|
| 263 |
4. [Prechorus] should create anticipation for [Chorus]
|
|
|
|
| 264 |
|
| 265 |
Enhance the lyrics for MAXIMUM emotional resonance.""",
|
| 266 |
|
| 267 |
+
"final_editor": f"""You are the final editor for HeartMuLa music production.
|
|
|
|
|
|
|
| 268 |
|
| 269 |
Your task: Output PERFECTLY FORMATTED, production-ready lyrics.
|
| 270 |
|
|
|
|
| 272 |
1. Output ONLY the actual lyrics with structure tags
|
| 273 |
2. Use HeartMuLa tags EXACTLY:
|
| 274 |
[Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo]
|
| 275 |
+
3. IMPORTANT: [Prechorus] NOT [Pre Chorus] - no space!
|
| 276 |
+
4. DO NOT include English translations in parentheses
|
| 277 |
+
5. DO NOT include explanations, descriptions, or markdown
|
| 278 |
+
6. DO NOT include lines starting with * or >
|
| 279 |
+
7. For [Inst] or [Solo] sections, write ONLY the tag
|
| 280 |
+
8. Ensure MINIMUM 6-8 different sections
|
| 281 |
+
9. Verify [Chorus] appears at least 2 times
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
OUTPUT ONLY CLEAN LYRICS WITH OPTIMAL TAG STRUCTURE."""
|
| 284 |
}
|
| 285 |
|
| 286 |
+
# ํ๊ทธ ์์ฑ ์์ด์ ํธ
|
| 287 |
+
TAG_AGENT = f"""You are a style tag generator for HeartMuLa.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
{HEARTMULA_TAG_GUIDE}
|
| 290 |
|
|
|
|
| 294 |
1. Tags must be comma-separated WITHOUT spaces: tag1,tag2,tag3
|
| 295 |
2. Use lowercase with underscores for multi-word: electric_guitar, hip_hop
|
| 296 |
3. Include 5-8 tags covering: instrument, mood, genre, tempo
|
| 297 |
+
4. Be specific: "piano" not "keyboard", "guitar" not "strings"
|
|
|
|
| 298 |
|
| 299 |
OUTPUT FORMAT (example):
|
| 300 |
+
piano,synthesizer,happy,pop,upbeat,romantic
|
|
|
|
|
|
|
| 301 |
|
| 302 |
+
Output ONLY the comma-separated tags, nothing else."""
|
| 303 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
|
| 305 |
+
# ============================================================
|
| 306 |
+
# ๐ง ์ ํธ๋ฆฌํฐ ํจ์
|
| 307 |
+
# ============================================================
|
| 308 |
|
| 309 |
def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") -> str:
|
| 310 |
+
"""Groq API ํธ์ถ"""
|
| 311 |
try:
|
| 312 |
client = Groq(api_key=api_key)
|
| 313 |
|
|
|
|
| 326 |
stream=False
|
| 327 |
)
|
| 328 |
|
| 329 |
+
if completion and completion.choices and completion.choices[0].message:
|
| 330 |
+
return completion.choices[0].message.content or "Error: Empty response"
|
| 331 |
+
return "Error: Invalid response"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
except Exception as e:
|
| 334 |
return f"Error: {str(e)}"
|
|
|
|
| 338 |
"""๊ฐ์ฌ ํ์ฒ๋ฆฌ - HeartMuLa ํฌ๋งท ์ต์ ํ"""
|
| 339 |
import re
|
| 340 |
|
| 341 |
+
if not text or not isinstance(text, str):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
return ""
|
| 343 |
|
| 344 |
lines = text.split('\n')
|
|
|
|
| 349 |
cleaned_lines.append('')
|
| 350 |
continue
|
| 351 |
|
| 352 |
+
# ์คํต ํจํด
|
| 353 |
+
skip_patterns = [r'^\s*\*', r'^\s*>', r'^\s*---', r'^\s*###', r'^\s*\*\*.*\*\*\s*$']
|
| 354 |
+
if any(re.match(p, line) for p in skip_patterns):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
continue
|
| 356 |
|
| 357 |
+
# ์ ๋ฆฌ
|
| 358 |
line = re.sub(r'\s*\([A-Za-z].*?\)\s*$', '', line)
|
| 359 |
line = re.sub(r'\*\*(.*?)\*\*', r'\1', line)
|
| 360 |
|
| 361 |
# HeartMuLa ํ๊ทธ ์ ๊ทํ
|
| 362 |
line = re.sub(r'\[Pre.?Chorus\]', '[Prechorus]', line, flags=re.IGNORECASE)
|
| 363 |
+
line = re.sub(r'\[Post.?Chorus\]', '[Chorus]', line, flags=re.IGNORECASE)
|
| 364 |
line = re.sub(r'\[Build.?Up\]', '[Prechorus]', line, flags=re.IGNORECASE)
|
| 365 |
line = re.sub(r'\[Break\]', '[Interlude]', line, flags=re.IGNORECASE)
|
|
|
|
| 366 |
|
| 367 |
if line.strip():
|
| 368 |
cleaned_lines.append(line.strip())
|
|
|
|
| 376 |
def clean_tags(tags: str) -> str:
|
| 377 |
"""ํ๊ทธ ์ ๋ฆฌ - HeartMuLa ํฌ๋งท (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์)"""
|
| 378 |
if not tags:
|
| 379 |
+
return "piano,happy,pop"
|
| 380 |
|
|
|
|
| 381 |
tags = tags.lower().strip()
|
|
|
|
|
|
|
| 382 |
tags = tags.replace(', ', ',').replace(' ,', ',').replace(' ', ' ')
|
| 383 |
+
tags = tags.replace(' ', '_').replace(',,', ',')
|
|
|
|
|
|
|
| 384 |
tags = tags.strip(',')
|
| 385 |
|
|
|
|
| 386 |
tag_list = [t.strip() for t in tags.split(',') if t.strip()]
|
| 387 |
unique_tags = list(dict.fromkeys(tag_list))
|
| 388 |
|
| 389 |
+
return ','.join(unique_tags) if unique_tags else "piano,happy,pop"
|
| 390 |
|
| 391 |
|
| 392 |
+
# ============================================================
|
| 393 |
+
# ๐ต ๋ฉ์ธ ํจ์๋ค
|
| 394 |
+
# ============================================================
|
| 395 |
+
|
| 396 |
@spaces.GPU(duration=120)
|
| 397 |
def generate_lyrics_soma(
|
| 398 |
api_key: str, theme: str, genre: str, mood: str,
|
| 399 |
+
language: str, vocal_type: str, additional: str,
|
| 400 |
+
progress=gr.Progress()
|
| 401 |
):
|
| 402 |
+
"""SOMA ๊ฐ์ฌ ์์ฑ"""
|
| 403 |
if not api_key or not api_key.strip():
|
| 404 |
return "โ Groq API Key ํ์", "", "", "", ""
|
| 405 |
if not theme or not theme.strip():
|
| 406 |
return "โ ์ฃผ์ ๋ฅผ ์
๋ ฅํ์ธ์", "", "", "", ""
|
| 407 |
|
| 408 |
+
base_prompt = f"""Create PROFESSIONAL lyrics optimized for HeartMuLa:
|
| 409 |
- Theme: {theme}
|
| 410 |
- Genre: {genre}
|
| 411 |
- Mood: {mood}
|
|
|
|
| 413 |
- Vocal Type: {vocal_type}
|
| 414 |
{f'- Additional: {additional}' if additional else ''}
|
| 415 |
|
| 416 |
+
USE HeartMuLa STRUCTURE TAGS:
|
| 417 |
+
[Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro]
|
| 418 |
+
|
| 419 |
+
IMPORTANT: Use [Prechorus] NOT [Pre Chorus] - no space!
|
| 420 |
|
| 421 |
+
REQUIRED STRUCTURE:
|
| 422 |
1. [Intro] - Set the mood
|
| 423 |
2. [Verse] x2-3 - Tell the story
|
| 424 |
+
3. [Prechorus] - Build tension
|
| 425 |
4. [Chorus] x2-3 - Main hook (MOST important!)
|
| 426 |
5. [Bridge] - Emotional contrast
|
| 427 |
+
6. [Outro] - Conclusion"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 428 |
|
| 429 |
try:
|
| 430 |
progress(0.2, desc="๐ค ์์ฌ๊ฐ - ์ด์ ์์ฑ...")
|
| 431 |
draft = call_groq(api_key, LYRICS_AGENTS["lyricist"], base_prompt)
|
| 432 |
if draft.startswith("Error:"):
|
| 433 |
+
return f"โ {draft}", "", "", "", ""
|
| 434 |
|
| 435 |
progress(0.4, desc="๐น ํ๋ก๋์ - ๊ตฌ์กฐ ์ต์ ํ...")
|
| 436 |
structured = call_groq(api_key, LYRICS_AGENTS["producer"],
|
| 437 |
+
f"Optimize for {genre}. Use [Prechorus] not [Pre Chorus]!", draft)
|
| 438 |
if structured.startswith("Error:"):
|
| 439 |
+
return f"โ {structured}", draft, "", "", ""
|
| 440 |
|
| 441 |
progress(0.6, desc="๐ซ ๊ฐ์ฑ ๋๋ ํฐ - ๏ฟฝ๏ฟฝ๏ฟฝ์ ๊ฐํ...")
|
| 442 |
emotional = call_groq(api_key, LYRICS_AGENTS["emotion_director"],
|
| 443 |
+
f"Enhance for {mood}.", structured)
|
| 444 |
if emotional.startswith("Error:"):
|
| 445 |
+
return f"โ {emotional}", draft, structured, "", ""
|
| 446 |
|
| 447 |
+
progress(0.8, desc="โจ ์ต์ข
ํธ์ง...")
|
| 448 |
final = call_groq(api_key, LYRICS_AGENTS["final_editor"],
|
| 449 |
+
"Output ONLY clean lyrics. Use [Prechorus] not [Pre Chorus]!", emotional)
|
| 450 |
if final.startswith("Error:"):
|
| 451 |
+
return f"โ {final}", draft, structured, emotional, ""
|
| 452 |
|
| 453 |
final_cleaned = clean_lyrics(final)
|
| 454 |
|
|
|
|
| 456 |
return "โ
๊ฐ์ฌ ์์ฑ ์๋ฃ!", draft, structured, emotional, final_cleaned
|
| 457 |
|
| 458 |
except Exception as e:
|
| 459 |
+
return f"โ ์์ธ: {str(e)}", "", "", "", ""
|
| 460 |
|
| 461 |
|
| 462 |
@spaces.GPU(duration=60)
|
| 463 |
def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str, vocal_type: str, additional: str):
|
| 464 |
+
"""๋น ๋ฅธ ๊ฐ์ฌ ์์ฑ"""
|
| 465 |
if not api_key or not api_key.strip():
|
| 466 |
+
return "โ API Key ํ์"
|
| 467 |
if not theme or not theme.strip():
|
| 468 |
return "โ ์ฃผ์ ๋ฅผ ์
๋ ฅํ์ธ์"
|
| 469 |
|
| 470 |
+
prompt = f"""Create song lyrics for HeartMuLa:
|
| 471 |
- Theme: {theme}
|
| 472 |
- Genre: {genre}
|
| 473 |
- Mood: {mood}
|
|
|
|
| 475 |
- Vocal: {vocal_type}
|
| 476 |
{f'- Special: {additional}' if additional else ''}
|
| 477 |
|
| 478 |
+
USE STRUCTURE (8-10 sections):
|
| 479 |
[Intro] โ [Verse] โ [Prechorus] โ [Chorus] โ [Verse] โ [Prechorus] โ [Chorus] โ [Bridge] โ [Chorus] โ [Outro]
|
| 480 |
|
| 481 |
+
IMPORTANT: Use [Prechorus] NOT [Pre Chorus]!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
|
| 483 |
+
OUTPUT ONLY lyrics with tags. NO explanations."""
|
| 484 |
|
| 485 |
try:
|
| 486 |
+
result = call_groq(api_key, f"""Professional songwriter for HeartMuLa.
|
|
|
|
| 487 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 488 |
+
Output ONLY clean lyrics.""", prompt)
|
| 489 |
|
| 490 |
if result.startswith("Error:"):
|
| 491 |
+
return f"โ {result}"
|
| 492 |
|
| 493 |
return clean_lyrics(result)
|
| 494 |
except Exception as e:
|
| 495 |
+
return f"โ {str(e)}"
|
| 496 |
|
| 497 |
|
| 498 |
+
@spaces.GPU(duration=60)
|
| 499 |
+
def generate_tags_ai(api_key: str, genre: str, mood: str, instruments: str):
|
| 500 |
+
"""AI ํ๊ทธ ์์ฑ"""
|
| 501 |
+
if not api_key:
|
| 502 |
return "piano,happy,pop"
|
| 503 |
|
| 504 |
prompt = f"""Generate HeartMuLa style tags:
|
| 505 |
- Genre: {genre}
|
| 506 |
- Mood: {mood}
|
| 507 |
+
- Instruments hint: {instruments}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 508 |
|
| 509 |
+
Output 5-8 comma-separated tags WITHOUT spaces.
|
| 510 |
+
Example: piano,synthesizer,happy,pop,upbeat"""
|
| 511 |
|
| 512 |
try:
|
| 513 |
+
result = call_groq(api_key, TAG_AGENT, prompt)
|
| 514 |
if result.startswith("Error:"):
|
| 515 |
return "piano,happy,pop"
|
| 516 |
return clean_tags(result)
|
|
|
|
| 518 |
return "piano,happy,pop"
|
| 519 |
|
| 520 |
|
| 521 |
+
@spaces.GPU(duration=180)
|
| 522 |
+
def generate_music_heartmula(
|
| 523 |
+
lyrics: str,
|
| 524 |
+
tags: str,
|
| 525 |
+
max_duration_seconds: int,
|
| 526 |
+
temperature: float,
|
| 527 |
+
topk: int,
|
| 528 |
+
cfg_scale: float,
|
| 529 |
+
progress=gr.Progress(track_tqdm=True),
|
| 530 |
):
|
| 531 |
+
"""HeartMuLa๋ก ์์
์์ฑ"""
|
| 532 |
+
if not lyrics or not lyrics.strip():
|
| 533 |
+
raise gr.Error("โ ๏ธ ๊ฐ์ฌ๋ฅผ ์
๋ ฅํ์ธ์!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 534 |
|
| 535 |
+
if not tags or not tags.strip():
|
| 536 |
+
raise gr.Error("โ ๏ธ ํ๊ทธ๋ฅผ ์
๋ ฅํ์ธ์!")
|
| 537 |
|
| 538 |
+
# ํ๊ทธ ์ ๋ฆฌ
|
| 539 |
+
tags = clean_tags(tags)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
|
| 541 |
+
# ์์ ํ์ผ ์์ฑ
|
| 542 |
+
with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
|
| 543 |
+
output_path = f.name
|
| 544 |
+
|
| 545 |
+
max_audio_length_ms = max_duration_seconds * 1000
|
| 546 |
+
|
| 547 |
try:
|
| 548 |
+
with torch.no_grad():
|
| 549 |
+
pipe(
|
| 550 |
+
{
|
| 551 |
+
"lyrics": lyrics,
|
| 552 |
+
"tags": tags,
|
| 553 |
+
},
|
| 554 |
+
max_audio_length_ms=max_audio_length_ms,
|
| 555 |
+
save_path=output_path,
|
| 556 |
+
topk=topk,
|
| 557 |
+
temperature=temperature,
|
| 558 |
+
cfg_scale=cfg_scale,
|
| 559 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
|
| 561 |
+
return output_path, f"โ
์์
์์ฑ ์๋ฃ! ({max_duration_seconds}์ด, ํ๊ทธ: {tags})"
|
| 562 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
except Exception as e:
|
| 564 |
+
return None, f"โ ์์ฑ ์คํจ: {str(e)}"
|
| 565 |
|
| 566 |
|
| 567 |
+
def load_tag_preset(preset_name):
|
| 568 |
+
"""ํ๊ทธ ํ๋ฆฌ์
๋ก๋"""
|
| 569 |
+
if preset_name in EXAMPLE_TAG_PRESETS:
|
| 570 |
+
return EXAMPLE_TAG_PRESETS[preset_name]
|
| 571 |
+
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
|
| 573 |
|
| 574 |
# ============================================================
|
| 575 |
+
# ๐จ Comic Classic Theme CSS
|
| 576 |
# ============================================================
|
| 577 |
|
| 578 |
css = """
|
|
|
|
| 579 |
@import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
|
| 580 |
|
|
|
|
| 581 |
.gradio-container {
|
| 582 |
background-color: #FEF9C3 !important;
|
| 583 |
+
background-image: radial-gradient(#1F2937 1px, transparent 1px) !important;
|
|
|
|
| 584 |
background-size: 20px 20px !important;
|
|
|
|
| 585 |
font-family: 'Comic Neue', cursive, sans-serif !important;
|
| 586 |
}
|
| 587 |
|
| 588 |
+
.huggingface-space-header, footer, .footer { display: none !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 589 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 590 |
.header-title h1 {
|
| 591 |
font-family: 'Bangers', cursive !important;
|
| 592 |
color: #1F2937 !important;
|
| 593 |
+
font-size: 3rem !important;
|
|
|
|
| 594 |
text-align: center !important;
|
| 595 |
+
text-shadow: 4px 4px 0px #FACC15, 6px 6px 0px #1F2937 !important;
|
|
|
|
|
|
|
|
|
|
| 596 |
letter-spacing: 3px !important;
|
| 597 |
-webkit-text-stroke: 2px #1F2937 !important;
|
| 598 |
}
|
| 599 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
.section-title {
|
| 601 |
font-family: 'Bangers', cursive !important;
|
| 602 |
color: #1F2937 !important;
|
| 603 |
+
font-size: 1.6rem !important;
|
| 604 |
border-bottom: 4px solid #3B82F6 !important;
|
| 605 |
padding-bottom: 8px !important;
|
|
|
|
| 606 |
text-shadow: 2px 2px 0px #FACC15 !important;
|
| 607 |
}
|
| 608 |
|
| 609 |
+
.gr-panel, .gr-box, .block {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 610 |
background: #FFFFFF !important;
|
| 611 |
border: 3px solid #1F2937 !important;
|
| 612 |
border-radius: 8px !important;
|
| 613 |
box-shadow: 6px 6px 0px #1F2937 !important;
|
|
|
|
| 614 |
}
|
| 615 |
|
| 616 |
+
.gr-panel:hover, .block:hover {
|
|
|
|
| 617 |
transform: translate(-2px, -2px) !important;
|
| 618 |
box-shadow: 8px 8px 0px #1F2937 !important;
|
| 619 |
}
|
| 620 |
|
| 621 |
+
textarea, input[type="text"], input[type="password"] {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
background: #FFFFFF !important;
|
| 623 |
border: 3px solid #1F2937 !important;
|
| 624 |
border-radius: 8px !important;
|
|
|
|
| 625 |
font-family: 'Comic Neue', cursive !important;
|
|
|
|
| 626 |
font-weight: 700 !important;
|
|
|
|
| 627 |
}
|
| 628 |
|
| 629 |
+
textarea:focus, input:focus {
|
|
|
|
|
|
|
|
|
|
| 630 |
border-color: #3B82F6 !important;
|
| 631 |
box-shadow: 4px 4px 0px #3B82F6 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 632 |
}
|
| 633 |
|
| 634 |
+
.gr-button-primary, button.primary {
|
|
|
|
|
|
|
|
|
|
| 635 |
background: #3B82F6 !important;
|
| 636 |
border: 3px solid #1F2937 !important;
|
| 637 |
border-radius: 8px !important;
|
| 638 |
color: #FFFFFF !important;
|
| 639 |
font-family: 'Bangers', cursive !important;
|
|
|
|
| 640 |
font-size: 1.2rem !important;
|
| 641 |
letter-spacing: 2px !important;
|
|
|
|
| 642 |
box-shadow: 5px 5px 0px #1F2937 !important;
|
|
|
|
| 643 |
text-shadow: 1px 1px 0px #1F2937 !important;
|
| 644 |
}
|
| 645 |
|
| 646 |
+
.gr-button-primary:hover {
|
|
|
|
|
|
|
| 647 |
background: #2563EB !important;
|
| 648 |
transform: translate(-2px, -2px) !important;
|
| 649 |
box-shadow: 7px 7px 0px #1F2937 !important;
|
| 650 |
}
|
| 651 |
|
| 652 |
+
.gr-button-secondary, button.secondary {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 653 |
background: #EF4444 !important;
|
| 654 |
border: 3px solid #1F2937 !important;
|
|
|
|
| 655 |
color: #FFFFFF !important;
|
| 656 |
font-family: 'Bangers', cursive !important;
|
|
|
|
|
|
|
|
|
|
| 657 |
box-shadow: 4px 4px 0px #1F2937 !important;
|
|
|
|
|
|
|
| 658 |
}
|
| 659 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 660 |
.generate-btn {
|
| 661 |
background: #10B981 !important;
|
| 662 |
border: 3px solid #1F2937 !important;
|
|
|
|
| 663 |
color: #FFFFFF !important;
|
| 664 |
font-family: 'Bangers', cursive !important;
|
| 665 |
+
font-size: 1.4rem !important;
|
|
|
|
|
|
|
| 666 |
box-shadow: 5px 5px 0px #1F2937 !important;
|
|
|
|
| 667 |
}
|
| 668 |
|
| 669 |
.generate-btn:hover {
|
| 670 |
background: #059669 !important;
|
| 671 |
transform: translate(-2px, -2px) !important;
|
|
|
|
| 672 |
}
|
| 673 |
|
|
|
|
| 674 |
.gr-accordion {
|
| 675 |
background: #FACC15 !important;
|
| 676 |
border: 3px solid #1F2937 !important;
|
|
|
|
| 677 |
box-shadow: 4px 4px 0px #1F2937 !important;
|
| 678 |
}
|
| 679 |
|
| 680 |
+
.tag-input textarea {
|
| 681 |
+
background: #FEF3C7 !important;
|
| 682 |
+
border: 3px dashed #F59E0B !important;
|
| 683 |
+
font-family: 'Courier New', monospace !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 684 |
}
|
| 685 |
|
| 686 |
+
.status-box textarea {
|
|
|
|
|
|
|
|
|
|
| 687 |
background: #1F2937 !important;
|
| 688 |
color: #10B981 !important;
|
| 689 |
font-family: 'Courier New', monospace !important;
|
| 690 |
border: 3px solid #10B981 !important;
|
|
|
|
|
|
|
| 691 |
}
|
| 692 |
|
| 693 |
+
label {
|
|
|
|
|
|
|
| 694 |
color: #1F2937 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 695 |
font-family: 'Comic Neue', cursive !important;
|
| 696 |
font-weight: 700 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 697 |
}
|
| 698 |
|
| 699 |
+
::-webkit-scrollbar { width: 12px; }
|
| 700 |
+
::-webkit-scrollbar-track { background: #FEF9C3; border: 2px solid #1F2937; }
|
| 701 |
+
::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; }
|
| 702 |
+
::selection { background: #FACC15; color: #1F2937; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 703 |
"""
|
| 704 |
|
| 705 |
+
|
| 706 |
# ============================================================
|
| 707 |
+
# ๐จ Gradio UI
|
| 708 |
# ============================================================
|
| 709 |
|
| 710 |
+
with gr.Blocks(css=css, title="๐ต SOMA Music Studio - HeartMuLa") as demo:
|
| 711 |
|
| 712 |
+
# Header
|
| 713 |
gr.HTML("""
|
| 714 |
<div style="text-align: center; margin: 20px 0 10px 0;">
|
| 715 |
+
<a href="https://huggingface.co/HeartMuLa" target="_blank">
|
| 716 |
+
<img src="https://img.shields.io/badge/๐ต_HeartMuLa-oss--3B-ff6b6b?style=for-the-badge&labelColor=1F2937" alt="HeartMuLa">
|
| 717 |
</a>
|
| 718 |
+
<a href="https://github.com/HeartMuLa/heartlib" target="_blank" style="margin-left: 10px;">
|
| 719 |
+
<img src="https://img.shields.io/badge/๐ฆ_heartlib-GitHub-3B82F6?style=for-the-badge&labelColor=1F2937" alt="GitHub">
|
| 720 |
</a>
|
| 721 |
</div>
|
| 722 |
""")
|
| 723 |
|
| 724 |
+
gr.Markdown("# ๐ต SOMA MUSIC STUDIO ๐ถ", elem_classes="header-title")
|
|
|
|
|
|
|
|
|
|
| 725 |
|
| 726 |
gr.Markdown("""
|
| 727 |
+
<p style="text-align: center; font-family: 'Comic Neue', cursive; font-size: 1.1rem; font-weight: 700; color: #1F2937;">
|
| 728 |
+
๐ซ HeartMuLa-oss-3B + SOMA Multi-Agent = ์ต๊ณ ํ์ง AI ์์
์์ฑ ๐ซ
|
| 729 |
+
</p>
|
| 730 |
""")
|
| 731 |
|
| 732 |
+
# API Key
|
| 733 |
+
with gr.Accordion("๐ Groq API Key (๊ฐ์ฌ ์์ฑ์ฉ)", open=False):
|
| 734 |
+
GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
|
| 735 |
+
groq_key = gr.Textbox(
|
| 736 |
+
label="Groq API Key",
|
| 737 |
+
type="password",
|
| 738 |
+
value=GROQ_KEY,
|
| 739 |
+
placeholder="gsk_..." if not GROQ_KEY else "โ
Secret ๋ก๋๋จ",
|
| 740 |
+
interactive=not bool(GROQ_KEY)
|
| 741 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
|
| 743 |
with gr.Row(equal_height=False):
|
| 744 |
# ========== ์ข์ธก: ๊ฐ์ฌ ์์ฑ ==========
|
|
|
|
| 747 |
|
| 748 |
theme_input = gr.Textbox(
|
| 749 |
label="๐ฏ ๋
ธ๋ ์ฃผ์ ",
|
| 750 |
+
placeholder="์: ์ด๋ณ ํ ์ฑ์ฅ, ๊ฟ์ ํฅํ ๋์ , ์ฌ๋์ ๊ณ ๋ฐฑ...",
|
| 751 |
lines=2
|
| 752 |
)
|
| 753 |
|
|
|
|
| 755 |
lyrics_genre = gr.Dropdown(
|
| 756 |
label="๐ธ ์ฅ๋ฅด",
|
| 757 |
choices=["K-Pop", "Pop", "R&B", "Hip-Hop", "Ballad", "Rock",
|
| 758 |
+
"EDM", "Jazz", "Folk", "Disco", "Cinematic"],
|
| 759 |
value="K-Pop"
|
| 760 |
)
|
| 761 |
lyrics_mood = gr.Dropdown(
|
| 762 |
label="๐ซ ๋ถ์๊ธฐ",
|
| 763 |
choices=["Empowering", "Melancholic", "Joyful", "Romantic",
|
| 764 |
+
"Energetic", "Dreamy", "Nostalgic", "Peaceful", "Confident"],
|
|
|
|
| 765 |
value="Empowering"
|
| 766 |
)
|
| 767 |
|
|
|
|
| 769 |
lyrics_language = gr.Dropdown(
|
| 770 |
label="๐ ์ธ์ด",
|
| 771 |
choices=["English", "Korean", "Korean + English", "Japanese"],
|
| 772 |
+
value="English"
|
| 773 |
)
|
| 774 |
lyrics_vocal_type = gr.Dropdown(
|
| 775 |
+
label="๐ค ๋ณด์ปฌ",
|
| 776 |
+
choices=["Solo Female", "Solo Male", "Duet", "Group/Choir"],
|
| 777 |
+
value="Solo Female"
|
|
|
|
| 778 |
)
|
| 779 |
|
| 780 |
lyrics_additional = gr.Textbox(
|
| 781 |
label="โจ ์ถ๊ฐ ์ง์ (์ ํ)",
|
| 782 |
+
placeholder="ํน๋ณ ์์ฒญ...",
|
| 783 |
lines=1
|
| 784 |
)
|
| 785 |
|
| 786 |
with gr.Row():
|
| 787 |
+
quick_btn = gr.Button("โก QUICK", variant="secondary")
|
| 788 |
soma_lyrics_btn = gr.Button("๐ง SOMA GENERATE", variant="primary")
|
| 789 |
|
| 790 |
lyrics_status = gr.Textbox(label="๐ ์ํ", interactive=False, max_lines=1, elem_classes="status-box")
|
| 791 |
|
| 792 |
with gr.Accordion("๐ SOMA ์์
๊ณผ์ ", open=False):
|
| 793 |
+
step1_out = gr.Textbox(label="1๏ธโฃ ์์ฌ๊ฐ", lines=3, interactive=False)
|
| 794 |
+
step2_out = gr.Textbox(label="2๏ธโฃ ํ๋ก๋์", lines=3, interactive=False)
|
| 795 |
+
step3_out = gr.Textbox(label="3๏ธโฃ ๊ฐ์ฑ", lines=3, interactive=False)
|
| 796 |
+
step4_out = gr.Textbox(label="4๏ธโฃ ์ต์ข
", lines=3, interactive=False)
|
|
|
|
|
|
|
|
|
|
| 797 |
|
| 798 |
final_lyrics = gr.Textbox(
|
| 799 |
+
label="โ๏ธ ๊ฐ์ฌ (ํธ์ง ๊ฐ๋ฅ)",
|
| 800 |
+
lines=16,
|
| 801 |
+
value=EXAMPLE_LYRICS,
|
| 802 |
+
placeholder="๊ฐ์ฌ๊ฐ ์ฌ๊ธฐ ํ์๋ฉ๋๋ค..."
|
| 803 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
|
| 805 |
# ========== ์ฐ์ธก: ์์
์์ฑ ==========
|
| 806 |
with gr.Column(scale=1, min_width=400):
|
| 807 |
gr.Markdown("## ๐ต MUSIC GENERATOR", elem_classes="section-title")
|
| 808 |
|
| 809 |
+
# ํ๊ทธ ํ๋ฆฌ์
|
| 810 |
+
gr.Markdown("### ๐ท๏ธ ํ๊ทธ ํ๋ฆฌ์
(ํด๋ฆญํ๋ฉด ์๋ ์
๋ ฅ)")
|
| 811 |
+
tag_preset = gr.Dropdown(
|
| 812 |
+
label="ํ๋ฆฌ์
์ ํ",
|
| 813 |
+
choices=list(EXAMPLE_TAG_PRESETS.keys()),
|
| 814 |
+
value=None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
)
|
| 816 |
|
| 817 |
+
tags_input = gr.Textbox(
|
| 818 |
+
label="๐น ์คํ์ผ ํ๊ทธ (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์)",
|
| 819 |
+
value=EXAMPLE_TAGS,
|
| 820 |
+
placeholder="piano,happy,uplifting,pop",
|
| 821 |
+
lines=1,
|
| 822 |
+
elem_classes="tag-input"
|
| 823 |
)
|
| 824 |
|
| 825 |
with gr.Row():
|
| 826 |
+
ai_tag_btn = gr.Button("๐ค AI ํ๊ทธ ์์ฑ", variant="secondary", size="sm")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 827 |
|
| 828 |
+
with gr.Accordion("โ๏ธ ์์ฑ ์ค์ ", open=True):
|
| 829 |
+
max_duration = gr.Slider(
|
| 830 |
+
minimum=30, maximum=240, value=120, step=10,
|
| 831 |
+
label="โฑ๏ธ ์ต๋ ๊ธธ์ด (์ด)",
|
| 832 |
+
info="30~240์ด"
|
|
|
|
| 833 |
)
|
| 834 |
+
|
| 835 |
+
with gr.Row():
|
| 836 |
+
temperature = gr.Slider(
|
| 837 |
+
minimum=0.1, maximum=2.0, value=1.0, step=0.1,
|
| 838 |
+
label="๐ก๏ธ Temperature",
|
| 839 |
+
info="๋์์๋ก ์ฐฝ์์ "
|
| 840 |
+
)
|
| 841 |
+
topk = gr.Slider(
|
| 842 |
+
minimum=1, maximum=100, value=50, step=1,
|
| 843 |
+
label="๐ฏ Top-K"
|
| 844 |
+
)
|
| 845 |
+
|
| 846 |
+
cfg_scale = gr.Slider(
|
| 847 |
+
minimum=1.0, maximum=3.0, value=1.5, step=0.1,
|
| 848 |
+
label="๐ CFG Scale",
|
| 849 |
+
info="Classifier-free guidance"
|
| 850 |
)
|
| 851 |
|
| 852 |
+
generate_btn = gr.Button(
|
| 853 |
+
"๐ถ GENERATE MUSIC!",
|
| 854 |
+
variant="primary",
|
| 855 |
+
size="lg",
|
| 856 |
+
elem_classes="generate-btn"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 857 |
)
|
| 858 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 859 |
music_status = gr.Textbox(label="๐ ์ํ", interactive=False, max_lines=1, elem_classes="status-box")
|
| 860 |
+
audio_output = gr.Audio(label="๐ง ์์ฑ๋ ์์
", type="filepath")
|
| 861 |
+
|
| 862 |
+
gr.Markdown("""
|
| 863 |
+
### ๐ก Tips
|
| 864 |
+
- **๊ตฌ์กฐ ํ๊ทธ:** [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Outro]
|
| 865 |
+
- **์คํ์ผ ํ๊ทธ:** piano,happy,uplifting,pop (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์!)
|
| 866 |
+
- **Temperature:** ๋์ผ๋ฉด ์ฐฝ์์ , ๋ฎ์ผ๋ฉด ์ผ๊ด์ฑ
|
| 867 |
+
- **๊ธธ์ด:** ๊ธด ๊ณก์ ์๊ฐ์ด ๋ ๊ฑธ๋ฆฝ๋๋ค
|
| 868 |
+
""")
|
| 869 |
+
|
| 870 |
+
# ๊ฐ์ด๋
|
| 871 |
+
with gr.Accordion("๐ HeartMuLa ๊ฐ์ด๋", open=False):
|
| 872 |
gr.Markdown(f"""
|
| 873 |
{HEARTMULA_TAG_GUIDE}
|
| 874 |
|
| 875 |
---
|
| 876 |
|
| 877 |
{HEARTMULA_LYRICS_STRUCTURE}
|
| 878 |
+
|
| 879 |
+
---
|
| 880 |
+
|
| 881 |
+
**Model:** [HeartMuLa-oss-3B](https://huggingface.co/HeartMuLa/HeartMuLa-oss-3B) |
|
| 882 |
+
**Paper:** [arXiv](https://arxiv.org/abs/2601.10547) |
|
| 883 |
+
**Code:** [GitHub](https://github.com/HeartMuLa/heartlib)
|
| 884 |
+
|
| 885 |
+
*Licensed under Apache 2.0*
|
| 886 |
""")
|
| 887 |
|
| 888 |
# ========== Event Handlers ==========
|
| 889 |
|
| 890 |
+
# ํ๊ทธ ํ๋ฆฌ์
๋ก๋
|
| 891 |
+
tag_preset.change(
|
| 892 |
+
fn=load_tag_preset,
|
| 893 |
+
inputs=[tag_preset],
|
| 894 |
+
outputs=[tags_input]
|
| 895 |
+
)
|
| 896 |
+
|
| 897 |
+
# AI ํ๊ทธ ์์ฑ
|
| 898 |
+
ai_tag_btn.click(
|
| 899 |
+
fn=generate_tags_ai,
|
| 900 |
+
inputs=[groq_key, lyrics_genre, lyrics_mood, tags_input],
|
| 901 |
+
outputs=[tags_input]
|
| 902 |
)
|
| 903 |
|
| 904 |
# ๋น ๋ฅธ ๊ฐ์ฌ ์์ฑ
|
|
|
|
| 919 |
outputs=[final_lyrics]
|
| 920 |
)
|
| 921 |
|
| 922 |
+
# ์์
์์ฑ (HeartMuLa)
|
| 923 |
+
generate_btn.click(
|
| 924 |
+
fn=generate_music_heartmula,
|
| 925 |
+
inputs=[final_lyrics, tags_input, max_duration, temperature, topk, cfg_scale],
|
| 926 |
+
outputs=[audio_output, music_status]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 927 |
)
|
| 928 |
|
| 929 |
|