| import os |
| import tempfile |
| import torch |
| import gradio as gr |
| from huggingface_hub import hf_hub_download, snapshot_download |
| import spaces |
| import requests |
| import json |
| from groq import Groq |
|
|
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| def download_models(): |
| """Download all required model files from HuggingFace Hub.""" |
| cache_dir = os.environ.get("HF_HOME", os.path.expanduser("/tmp")) |
| model_dir = os.path.join(cache_dir, "heartmula_models") |
|
|
| if not os.path.exists(model_dir): |
| os.makedirs(model_dir, exist_ok=True) |
|
|
| |
| print("Downloading HeartMuLaGen files...") |
| for filename in ["tokenizer.json", "gen_config.json"]: |
| hf_hub_download( |
| repo_id="HeartMuLa/HeartMuLaGen", |
| filename=filename, |
| local_dir=model_dir, |
| ) |
|
|
| |
| print("Downloading HeartMuLa-oss-3B...") |
| snapshot_download( |
| repo_id="HeartMuLa/HeartMuLa-oss-3B", |
| local_dir=os.path.join(model_dir, "HeartMuLa-oss-3B"), |
| ) |
|
|
| |
| print("Downloading HeartCodec-oss...") |
| snapshot_download( |
| repo_id="HeartMuLa/HeartCodec-oss", |
| local_dir=os.path.join(model_dir, "HeartCodec-oss"), |
| ) |
|
|
| print("All models downloaded successfully!") |
| return model_dir |
|
|
| from heartlib import HeartMuLaGenPipeline |
|
|
| |
| model_dir = download_models() |
|
|
| |
| if torch.cuda.is_available(): |
| device = torch.device("cuda") |
| dtype = torch.bfloat16 |
| else: |
| device = torch.device("cpu") |
| dtype = torch.float32 |
|
|
| print(f"Loading HeartMuLa pipeline on {device} with {dtype}...") |
| pipe = HeartMuLaGenPipeline.from_pretrained( |
| model_dir, |
| device=device, |
| dtype=dtype, |
| version="3B", |
| ) |
| print("HeartMuLa Pipeline loaded successfully!") |
|
|
|
|
| |
| |
| |
|
|
| |
| STRUCTURE_TAGS = [ |
| "[Intro]", "[Verse]", "[Prechorus]", "[Chorus]", "[Bridge]", |
| "[Interlude]", "[Hook]", "[Outro]", "[Inst]", "[Solo]" |
| ] |
|
|
| |
| HEARTMULA_TAG_GUIDE = """ |
| ## ๐ผ HeartMuLa ์
๋ ฅ ํ์ |
| |
| ### โ ๏ธ ์ค์: HeartMuLa๋ 2๊ฐ์ง ์
๋ ฅ๋ง ๋ฐ์ต๋๋ค! |
| 1. **๊ฐ์ฌ (Lyrics)** - ๊ตฌ์กฐ ํ๊ทธ ํฌํจ |
| 2. **์คํ์ผ ํ๊ทธ (Tags)** - ์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์ |
| |
| โ ๋ณด์ปฌ ํ์
(๋์ฃ, ๊ทธ๋ฃน ๋ฑ)์ ์ง์ํ์ง ์์ต๋๋ค! |
| |
| ### ๐ ๊ตฌ์กฐ ํ๊ทธ (๊ฐ์ฌ ๋ด ์ฌ์ฉ): |
| `[Intro]`, `[Verse]`, `[Prechorus]`, `[Chorus]`, `[Bridge]`, `[Outro]` |
| |
| ### ๐ท๏ธ ์คํ์ผ ํ๊ทธ ํ์ (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์): |
| ``` |
| piano,happy,wedding,synthesizer,romantic |
| energetic,drums,driving,rock,electric guitar |
| soft,Sad,Ballad,Longing |
| ``` |
| |
| ### ๐น ํ๊ทธ ์นดํ
๊ณ ๋ฆฌ (์ค์ ๋ฐ๋ชจ ๊ธฐ๋ฐ): |
| |
| **์
๊ธฐ (Instruments):** |
| Piano, Keyboard, synthesizer, drum machine, electric guitar, acoustic guitar, drums, Strings, bass |
| |
| **์ฅ๋ฅด (Genre):** |
| pop, R&B, rock, Ballad, electronic, acoustic |
| |
| **๋ถ์๊ธฐ/๊ฐ์ (Mood/Emotion):** |
| warm, reflection, energetic, soft, Sad, Regret, Longing, meditation, faith, peaceful, Romantic, emotional, hope, hopeful, powerful, epic, driving, happy, uplifting, dreamy |
| |
| **์ํฉ (Situation):** |
| Cafe, wedding, Walking, self-discovery |
| |
| ### ๐ ์ง์ ์ธ์ด: |
| ๐บ๐ธ English | ๐ฐ๐ท Korean | ๐จ๐ณ Chinese | ๐ฏ๐ต Japanese | ๐ช๐ธ Spanish |
| """ |
|
|
| |
| HEARTMULA_LYRICS_STRUCTURE = """ |
| ## ๐ HeartMuLa Recommended Lyrics Structure |
| |
| ### OPTIMAL FORMAT: |
| ``` |
| [Intro] |
| |
| [Verse] |
| First verse lyrics here |
| Second line of first verse |
| |
| [Prechorus] |
| Building tension here |
| Leading to the chorus |
| |
| [Chorus] |
| Main hook and memorable melody |
| Most important part of song |
| |
| [Verse] |
| Second verse develops story |
| |
| [Bridge] |
| Contrast section here |
| Different melody or perspective |
| |
| [Chorus] |
| Main hook repeated |
| |
| [Outro] |
| Closing the song |
| ``` |
| |
| ### KEY RULES: |
| 1. [Chorus] appears at least 2-3 times |
| 2. [Prechorus] builds tension before [Chorus] |
| 3. [Bridge] provides contrast before final [Chorus] |
| 4. Use [Prechorus] NOT [Pre Chorus] (no space!) |
| """ |
|
|
| |
| EXAMPLE_LYRICS = """[Intro] |
| |
| [Verse] |
| The sun creeps in across the floor |
| I hear the traffic outside the door |
| The coffee pot begins to hiss |
| It is another morning just like this |
| |
| [Prechorus] |
| The world keeps spinning round and round |
| Feet are planted on the ground |
| I find my rhythm in the sound |
| |
| [Chorus] |
| Every day the light returns |
| Every day the fire burns |
| We keep on walking down this street |
| Moving to the same steady beat |
| It is the ordinary magic that we meet |
| |
| [Verse] |
| The hours tick deeply into noon |
| Chasing shadows, chasing the moon |
| Work is done and the lights go low |
| Watching the city start to glow |
| |
| [Bridge] |
| It is not always easy, not always bright |
| Sometimes we wrestle with the night |
| But we make it to the morning light |
| |
| [Chorus] |
| Every day the light returns |
| Every day the fire burns |
| We keep on walking down this street |
| Moving to the same steady beat |
| |
| [Outro] |
| Just another day |
| Every single day""" |
|
|
| EXAMPLE_TAGS = "piano,happy,uplifting,pop" |
|
|
| |
| EXAMPLE_TAG_PRESETS = { |
| "โ Cafe Warm Pop": "warm,reflection,pop,Cafe", |
| "๐น R&B Soft Regret": "R&B,Keyboard,Regret,drum machine,electric guitar,synthesizer,soft", |
| "โก Electronic Energetic": "energetic,electronic,synthesizer,drum machine,self-discovery", |
| "๐ข Sad Ballad Longing": "soft,Sad,Ballad,Longing", |
| "๐ง Meditation Peaceful": "meditation,faith,acoustic,peaceful", |
| "๐ Wedding Romantic": "wedding,Piano,Strings,acoustic guitar,pop,Romantic", |
| "๐ Emotional Piano": "Sad,Regret,electric guitar,drums,Piano,emotional", |
| "๐ถ Hopeful Acoustic": "Walking,acoustic guitar,Strings,hope,hopeful", |
| "๐ธ Epic Electronic": "synthesizer,drum machine,powerful,epic,Keyboard,electronic", |
| "๐ฅ Driving Rock": "energetic,drums,driving,rock,electric guitar", |
| "๐ต Happy Uplifting": "piano,happy,uplifting,pop", |
| "๐ Soft Dreamy": "soft,dreamy,piano,strings,peaceful" |
| } |
|
|
|
|
| |
| |
| |
|
|
| LYRICS_AGENTS = { |
| "lyricist": f"""You are a master lyricist optimized for HeartMuLa music generation. |
| |
| {HEARTMULA_LYRICS_STRUCTURE} |
| |
| Your task: Create powerful, memorable lyrics with OPTIMAL HeartMuLa tag placement. |
| |
| CRITICAL RULES: |
| 1. Use HeartMuLa structure tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo] |
| 2. [Prechorus] (not [Pre Chorus]) - HeartMuLa format, NO SPACE! |
| 3. Ensure [Chorus] is the most memorable, singable part |
| 4. Include [Prechorus] to build tension before [Chorus] |
| 5. Add [Bridge] for emotional contrast |
| 6. Minimum 6-8 sections for quality generation |
| 7. โ ๏ธ CRITICAL: Write lyrics in the EXACT language specified by the user! |
| - If Korean is specified, write ALL lyrics in Korean (ํ๊ตญ์ด)! |
| - Do NOT translate or write in English unless explicitly asked! |
| |
| Write lyrics that create the BEST POSSIBLE foundation for high-quality music generation.""", |
|
|
| "producer": f"""You are a music producer specializing in song structure optimization for HeartMuLa. |
| |
| {HEARTMULA_LYRICS_STRUCTURE} |
| |
| Your task: Analyze and OPTIMIZE the tag structure for MAXIMUM musical impact. |
| |
| CRITICAL OPTIMIZATION RULES: |
| 1. Use HeartMuLa tags: [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro] |
| 2. IMPORTANT: Use [Prechorus] NOT [Pre Chorus] - no space! |
| 3. Verify the structure follows genre conventions |
| 4. Ensure proper tag sequence (VerseโPrechorusโChorus) |
| 5. Check that [Chorus] appears at least 2-3 times |
| 6. Verify [Bridge] provides contrast before final chorus |
| |
| Output the restructured lyrics with OPTIMAL HeartMuLa tag placement.""", |
|
|
| "emotion_director": f"""You are an emotion director for music production. |
| |
| Your task: Enhance emotional impact through STRATEGIC tag content. |
| |
| EMOTIONAL MAPPING BY TAG: |
| - [Intro]: Intrigue, anticipation |
| - [Verse]: Storytelling, building connection |
| - [Prechorus]: Rising tension, excitement |
| - [Chorus]: Peak emotion, catharsis |
| - [Bridge]: Vulnerability, reflection, contrast |
| - [Outro]: Resolution, lingering feeling |
| |
| OPTIMIZATION RULES: |
| 1. Each [Verse] should progress emotionally |
| 2. [Chorus] must deliver the strongest emotional punch |
| 3. [Bridge] should offer new emotional perspective |
| 4. [Prechorus] should create anticipation for [Chorus] |
| |
| Enhance the lyrics for MAXIMUM emotional resonance.""", |
|
|
| "final_editor": f"""You are the final editor for HeartMuLa music production. |
| |
| Your task: Output PERFECTLY FORMATTED, production-ready lyrics. |
| |
| CRITICAL OUTPUT RULES: |
| 1. Output ONLY the actual lyrics with structure tags |
| 2. Use HeartMuLa tags EXACTLY: |
| [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro], [Inst], [Solo] |
| 3. IMPORTANT: [Prechorus] NOT [Pre Chorus] - no space! |
| 4. DO NOT include English translations in parentheses |
| 5. DO NOT include explanations, descriptions, or markdown |
| 6. DO NOT include lines starting with * or > |
| 7. For [Inst] or [Solo] sections, write ONLY the tag |
| 8. Ensure MINIMUM 6-8 different sections |
| 9. Verify [Chorus] appears at least 2 times |
| 10. PRESERVE THE ORIGINAL LANGUAGE - if lyrics are in Korean, output in Korean! |
| |
| OUTPUT ONLY CLEAN LYRICS WITH OPTIMAL TAG STRUCTURE IN THE ORIGINAL LANGUAGE.""" |
| } |
|
|
| |
| TAG_AGENT = f"""You are a style tag generator for HeartMuLa music generation. |
| |
| HeartMuLa uses comma-separated tags WITHOUT spaces. |
| |
| VALID TAG CATEGORIES (from official demo): |
| |
| **Instruments:** |
| Piano, Keyboard, synthesizer, drum machine, electric guitar, acoustic guitar, drums, Strings, bass |
| |
| **Genre:** |
| pop, R&B, rock, Ballad, electronic, acoustic |
| |
| **Mood/Emotion:** |
| warm, reflection, energetic, soft, Sad, Regret, Longing, meditation, faith, peaceful, Romantic, emotional, hope, hopeful, powerful, epic, driving, happy, uplifting, dreamy |
| |
| **Situation:** |
| Cafe, wedding, Walking, self-discovery |
| |
| RULES: |
| 1. Output 4-6 tags, comma-separated, NO SPACES between tags |
| 2. Use ONLY tags from the categories above |
| 3. Match the requested genre and mood |
| |
| OUTPUT FORMAT (examples): |
| piano,happy,uplifting,pop |
| warm,reflection,pop,Cafe |
| energetic,drums,driving,rock,electric guitar |
| soft,Sad,Ballad,Longing |
| |
| Output ONLY the comma-separated tags, nothing else.""" |
|
|
|
|
| |
| |
| |
|
|
| def call_groq(api_key: str, system: str, user_prompt: str, context: str = "") -> str: |
| """Groq API ํธ์ถ""" |
| try: |
| client = Groq(api_key=api_key) |
| |
| messages = [{"role": "system", "content": system}] |
| if context: |
| messages.append({"role": "user", "content": f"Previous work:\n{context}\n\nTask: {user_prompt}"}) |
| else: |
| messages.append({"role": "user", "content": user_prompt}) |
| |
| completion = client.chat.completions.create( |
| model="llama-3.3-70b-versatile", |
| messages=messages, |
| temperature=0.9, |
| max_completion_tokens=4096, |
| top_p=1, |
| stream=False |
| ) |
| |
| if completion and completion.choices and completion.choices[0].message: |
| return completion.choices[0].message.content or "Error: Empty response" |
| return "Error: Invalid response" |
| |
| except Exception as e: |
| return f"Error: {str(e)}" |
|
|
|
|
| def clean_lyrics(text: str) -> str: |
| """๊ฐ์ฌ ํ์ฒ๋ฆฌ - HeartMuLa ํฌ๋งท ์ต์ ํ""" |
| import re |
| |
| if not text or not isinstance(text, str): |
| return "" |
| |
| lines = text.split('\n') |
| cleaned_lines = [] |
| |
| for line in lines: |
| if not line.strip(): |
| cleaned_lines.append('') |
| continue |
| |
| |
| skip_patterns = [r'^\s*\*', r'^\s*>', r'^\s*---', r'^\s*###', r'^\s*\*\*.*\*\*\s*$'] |
| if any(re.match(p, line) for p in skip_patterns): |
| continue |
| |
| |
| line = re.sub(r'\s*\([A-Za-z].*?\)\s*$', '', line) |
| line = re.sub(r'\*\*(.*?)\*\*', r'\1', line) |
| |
| |
| line = re.sub(r'\[Pre.?Chorus\]', '[Prechorus]', line, flags=re.IGNORECASE) |
| line = re.sub(r'\[Post.?Chorus\]', '[Chorus]', line, flags=re.IGNORECASE) |
| line = re.sub(r'\[Build.?Up\]', '[Prechorus]', line, flags=re.IGNORECASE) |
| line = re.sub(r'\[Break\]', '[Interlude]', line, flags=re.IGNORECASE) |
| |
| if line.strip(): |
| cleaned_lines.append(line.strip()) |
| |
| result = '\n'.join(cleaned_lines) |
| result = re.sub(r'\n{3,}', '\n\n', result) |
| |
| return result.strip() |
|
|
|
|
| def clean_tags(tags: str) -> str: |
| """ํ๊ทธ ์ ๋ฆฌ - HeartMuLa ํฌ๋งท (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์)""" |
| if not tags: |
| return "piano,happy,pop" |
| |
| tags = tags.lower().strip() |
| tags = tags.replace(', ', ',').replace(' ,', ',').replace(' ', ' ') |
| tags = tags.replace(' ', '_').replace(',,', ',') |
| tags = tags.strip(',') |
| |
| tag_list = [t.strip() for t in tags.split(',') if t.strip()] |
| unique_tags = list(dict.fromkeys(tag_list)) |
| |
| return ','.join(unique_tags) if unique_tags else "piano,happy,pop" |
|
|
|
|
| |
| |
| |
|
|
| @spaces.GPU(duration=120) |
| def generate_lyrics_soma( |
| api_key: str, theme: str, genre: str, mood: str, |
| language: str, additional: str, |
| progress=gr.Progress() |
| ): |
| """SOMA ๊ฐ์ฌ ์์ฑ""" |
| if not api_key or not api_key.strip(): |
| return "โ Groq API Key ํ์", "", "", "", "" |
| if not theme or not theme.strip(): |
| return "โ ์ฃผ์ ๋ฅผ ์
๋ ฅํ์ธ์", "", "", "", "" |
| |
| base_prompt = f"""Create PROFESSIONAL lyrics optimized for HeartMuLa: |
| - Theme: {theme} |
| - Genre: {genre} |
| - Mood: {mood} |
| - Language: {language} โ CRITICAL: You MUST write ALL lyrics in {language} language! |
| {f'- Additional: {additional}' if additional else ''} |
| |
| โ ๏ธ LANGUAGE REQUIREMENT: Write the ENTIRE lyrics in {language}! |
| {"- ๋ฐ๋์ ํ๊ตญ์ด๋ก ๊ฐ์ฌ๋ฅผ ์์ฑํ์ธ์! ํ๊ธ๋ก๋ง ์์ฑ!" if language == "Korean" else ""} |
| {"- ่ฏท็จไธญๆๅๆๆๆญ่ฏ๏ผๅฟ
้กปๅ
จ้จ็จไธญๆ๏ผ" if language == "Chinese" else ""} |
| {"- ๆฅๆฌ่ชใงๆญ่ฉใๆธใใฆใใ ใใ๏ผๅ
จใฆๆฅๆฌ่ชใง๏ผ" if language == "Japanese" else ""} |
| {"- ยกEscribe toda la letra en espaรฑol! ยกSolo en espaรฑol!" if language == "Spanish" else ""} |
| |
| USE HeartMuLa STRUCTURE TAGS: |
| [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Interlude], [Hook], [Outro] |
| |
| IMPORTANT: Use [Prechorus] NOT [Pre Chorus] - no space! |
| |
| REQUIRED STRUCTURE: |
| 1. [Intro] - Set the mood |
| 2. [Verse] x2-3 - Tell the story |
| 3. [Prechorus] - Build tension |
| 4. [Chorus] x2-3 - Main hook (MOST important!) |
| 5. [Bridge] - Emotional contrast |
| 6. [Outro] - Conclusion |
| |
| REMEMBER: ALL lyrics must be in {language}!""" |
|
|
| try: |
| progress(0.2, desc="๐ค ์์ฌ๊ฐ - ์ด์ ์์ฑ...") |
| draft = call_groq(api_key, LYRICS_AGENTS["lyricist"], base_prompt) |
| if draft.startswith("Error:"): |
| return f"โ {draft}", "", "", "", "" |
| |
| progress(0.4, desc="๐น ํ๋ก๋์ - ๊ตฌ์กฐ ์ต์ ํ...") |
| structured = call_groq(api_key, LYRICS_AGENTS["producer"], |
| f"Optimize for {genre}. Use [Prechorus] not [Pre Chorus]! KEEP ALL LYRICS IN {language}!", draft) |
| if structured.startswith("Error:"): |
| return f"โ {structured}", draft, "", "", "" |
| |
| progress(0.6, desc="๐ซ ๊ฐ์ฑ ๋๋ ํฐ - ๊ฐ์ ๊ฐํ...") |
| emotional = call_groq(api_key, LYRICS_AGENTS["emotion_director"], |
| f"Enhance for {mood}. KEEP ALL LYRICS IN {language}!", structured) |
| if emotional.startswith("Error:"): |
| return f"โ {emotional}", draft, structured, "", "" |
| |
| progress(0.8, desc="โจ ์ต์ข
ํธ์ง...") |
| lang_instruction = { |
| "Korean": "ํ๊ตญ์ด๋ก๋ง ์ถ๋ ฅํ์ธ์!", |
| "Chinese": "็จไธญๆ่พๅบ๏ผ", |
| "Japanese": "ๆฅๆฌ่ชใงๅบๅใใฆใใ ใใ๏ผ", |
| "Spanish": "ยกEscribe en espaรฑol!" |
| }.get(language, "") |
| final = call_groq(api_key, LYRICS_AGENTS["final_editor"], |
| f"Output ONLY clean lyrics in {language}. Use [Prechorus] not [Pre Chorus]! {lang_instruction}", emotional) |
| if final.startswith("Error:"): |
| return f"โ {final}", draft, structured, emotional, "" |
| |
| final_cleaned = clean_lyrics(final) |
| |
| progress(1.0, desc="โ
์๋ฃ!") |
| return "โ
๊ฐ์ฌ ์์ฑ ์๋ฃ!", draft, structured, emotional, final_cleaned |
| |
| except Exception as e: |
| return f"โ ์์ธ: {str(e)}", "", "", "", "" |
|
|
|
|
| @spaces.GPU(duration=60) |
| def quick_lyrics(api_key: str, theme: str, genre: str, mood: str, language: str, additional: str): |
| """๋น ๋ฅธ ๊ฐ์ฌ ์์ฑ""" |
| if not api_key or not api_key.strip(): |
| return "โ API Key ํ์" |
| if not theme or not theme.strip(): |
| return "โ ์ฃผ์ ๋ฅผ ์
๋ ฅํ์ธ์" |
| |
| prompt = f"""Create song lyrics for HeartMuLa: |
| - Theme: {theme} |
| - Genre: {genre} |
| - Mood: {mood} |
| - Language: {language} โ CRITICAL: Write ALL lyrics in {language}! |
| {f'- Special: {additional}' if additional else ''} |
| |
| โ ๏ธ LANGUAGE: You MUST write the ENTIRE lyrics in {language}! |
| {"๋ฐ๋์ ํ๊ตญ์ด๋ก ์์ฑํ์ธ์! ํ๊ธ ๊ฐ์ฌ๋ง!" if language == "Korean" else ""} |
| {"่ฏท็จไธญๆๅๆญ่ฏ๏ผ" if language == "Chinese" else ""} |
| {"ๆฅๆฌ่ชใงๆธใใฆใใ ใใ๏ผ" if language == "Japanese" else ""} |
| {"ยกEscribe en espaรฑol!" if language == "Spanish" else ""} |
| |
| USE STRUCTURE (8-10 sections): |
| [Intro] โ [Verse] โ [Prechorus] โ [Chorus] โ [Verse] โ [Prechorus] โ [Chorus] โ [Bridge] โ [Chorus] โ [Outro] |
| |
| IMPORTANT: Use [Prechorus] NOT [Pre Chorus]! |
| |
| OUTPUT ONLY lyrics with tags in {language}. NO explanations.""" |
|
|
| try: |
| result = call_groq(api_key, f"""Professional songwriter for HeartMuLa. |
| {HEARTMULA_LYRICS_STRUCTURE} |
| Output ONLY clean lyrics.""", prompt) |
| |
| if result.startswith("Error:"): |
| return f"โ {result}" |
| |
| return clean_lyrics(result) |
| except Exception as e: |
| return f"โ {str(e)}" |
|
|
|
|
| @spaces.GPU(duration=60) |
| def generate_tags_ai(api_key: str, genre: str, mood: str, current_tags: str): |
| """AI ํ๊ทธ ์์ฑ - HeartMuLa ํ์""" |
| if not api_key: |
| return "piano,happy,pop" |
| |
| prompt = f"""Generate HeartMuLa style tags based on: |
| - Genre: {genre} |
| - Mood: {mood} |
| - Current hint: {current_tags} |
| |
| Use ONLY these tag categories (from HeartMuLa demo): |
| - Instruments: Piano, Keyboard, synthesizer, drum machine, electric guitar, acoustic guitar, drums, Strings, bass |
| - Genre: pop, R&B, rock, Ballad, electronic, acoustic |
| - Mood: warm, reflection, energetic, soft, Sad, Regret, Longing, meditation, faith, peaceful, Romantic, emotional, hope, hopeful, powerful, epic, driving, happy, uplifting, dreamy |
| - Situation: Cafe, wedding, Walking, self-discovery |
| |
| Output 4-6 comma-separated tags WITHOUT spaces between tags. |
| Example: piano,happy,uplifting,pop,Romantic""" |
|
|
| try: |
| result = call_groq(api_key, TAG_AGENT, prompt) |
| if result.startswith("Error:"): |
| return "piano,happy,pop" |
| return clean_tags(result) |
| except: |
| return "piano,happy,pop" |
|
|
|
|
| @spaces.GPU(duration=180) |
| def generate_music_heartmula( |
| lyrics: str, |
| tags: str, |
| max_duration_seconds: int, |
| temperature: float, |
| topk: int, |
| cfg_scale: float, |
| progress=gr.Progress(track_tqdm=True), |
| ): |
| """HeartMuLa๋ก ์์
์์ฑ""" |
| if not lyrics or not lyrics.strip(): |
| raise gr.Error("โ ๏ธ ๊ฐ์ฌ๋ฅผ ์
๋ ฅํ์ธ์!") |
|
|
| if not tags or not tags.strip(): |
| raise gr.Error("โ ๏ธ ํ๊ทธ๋ฅผ ์
๋ ฅํ์ธ์!") |
|
|
| |
| tags = clean_tags(tags) |
| |
| |
| with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: |
| output_path = f.name |
|
|
| max_audio_length_ms = max_duration_seconds * 1000 |
|
|
| try: |
| with torch.no_grad(): |
| pipe( |
| { |
| "lyrics": lyrics, |
| "tags": tags, |
| }, |
| max_audio_length_ms=max_audio_length_ms, |
| save_path=output_path, |
| topk=topk, |
| temperature=temperature, |
| cfg_scale=cfg_scale, |
| ) |
| |
| return output_path, f"โ
์์
์์ฑ ์๋ฃ! ({max_duration_seconds}์ด, ํ๊ทธ: {tags})" |
| |
| except Exception as e: |
| return None, f"โ ์์ฑ ์คํจ: {str(e)}" |
|
|
|
|
| def load_tag_preset(preset_name): |
| """ํ๊ทธ ํ๋ฆฌ์
๋ก๋""" |
| if preset_name in EXAMPLE_TAG_PRESETS: |
| return EXAMPLE_TAG_PRESETS[preset_name] |
| return "" |
|
|
|
|
| |
| |
| |
|
|
| css = """ |
| @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap'); |
| |
| .gradio-container { |
| background-color: #FEF9C3 !important; |
| background-image: radial-gradient(#1F2937 1px, transparent 1px) !important; |
| background-size: 20px 20px !important; |
| font-family: 'Comic Neue', cursive, sans-serif !important; |
| } |
| |
| .huggingface-space-header, footer, .footer { display: none !important; } |
| |
| .header-title h1 { |
| font-family: 'Bangers', cursive !important; |
| color: #1F2937 !important; |
| font-size: 3rem !important; |
| text-align: center !important; |
| text-shadow: 4px 4px 0px #FACC15, 6px 6px 0px #1F2937 !important; |
| letter-spacing: 3px !important; |
| -webkit-text-stroke: 2px #1F2937 !important; |
| } |
| |
| .section-title { |
| font-family: 'Bangers', cursive !important; |
| color: #1F2937 !important; |
| font-size: 1.6rem !important; |
| border-bottom: 4px solid #3B82F6 !important; |
| padding-bottom: 8px !important; |
| text-shadow: 2px 2px 0px #FACC15 !important; |
| } |
| |
| .gr-panel, .gr-box, .block { |
| background: #FFFFFF !important; |
| border: 3px solid #1F2937 !important; |
| border-radius: 8px !important; |
| box-shadow: 6px 6px 0px #1F2937 !important; |
| } |
| |
| textarea, input[type="text"], input[type="password"] { |
| background: #FFFFFF !important; |
| border: 3px solid #1F2937 !important; |
| border-radius: 8px !important; |
| font-family: 'Comic Neue', cursive !important; |
| font-weight: 700 !important; |
| } |
| |
| textarea:focus, input:focus { |
| border-color: #3B82F6 !important; |
| box-shadow: 4px 4px 0px #3B82F6 !important; |
| } |
| |
| .gr-button-primary, button.primary { |
| background: #3B82F6 !important; |
| border: 3px solid #1F2937 !important; |
| border-radius: 8px !important; |
| color: #FFFFFF !important; |
| font-family: 'Bangers', cursive !important; |
| font-size: 1.2rem !important; |
| letter-spacing: 2px !important; |
| box-shadow: 5px 5px 0px #1F2937 !important; |
| text-shadow: 1px 1px 0px #1F2937 !important; |
| } |
| |
| .gr-button-primary:hover { |
| background: #2563EB !important; |
| } |
| |
| .gr-button-secondary, button.secondary { |
| background: #EF4444 !important; |
| border: 3px solid #1F2937 !important; |
| color: #FFFFFF !important; |
| font-family: 'Bangers', cursive !important; |
| box-shadow: 4px 4px 0px #1F2937 !important; |
| } |
| |
| .generate-btn { |
| background: #10B981 !important; |
| border: 3px solid #1F2937 !important; |
| color: #FFFFFF !important; |
| font-family: 'Bangers', cursive !important; |
| font-size: 1.4rem !important; |
| box-shadow: 5px 5px 0px #1F2937 !important; |
| } |
| |
| .generate-btn:hover { |
| background: #059669 !important; |
| } |
| |
| .gr-accordion { |
| background: #FACC15 !important; |
| border: 3px solid #1F2937 !important; |
| box-shadow: 4px 4px 0px #1F2937 !important; |
| } |
| |
| .tag-input textarea { |
| background: #FEF3C7 !important; |
| border: 3px dashed #F59E0B !important; |
| font-family: 'Courier New', monospace !important; |
| } |
| |
| .status-box textarea { |
| background: #1F2937 !important; |
| color: #10B981 !important; |
| font-family: 'Courier New', monospace !important; |
| border: 3px solid #10B981 !important; |
| } |
| |
| label { |
| color: #1F2937 !important; |
| font-family: 'Comic Neue', cursive !important; |
| font-weight: 700 !important; |
| } |
| |
| ::-webkit-scrollbar { width: 12px; } |
| ::-webkit-scrollbar-track { background: #FEF9C3; border: 2px solid #1F2937; } |
| ::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; } |
| ::selection { background: #FACC15; color: #1F2937; } |
| """ |
|
|
|
|
| |
| |
| |
|
|
| with gr.Blocks(css=css, title="๐ต SOMA Music Studio - HeartMuLa") as demo: |
| |
| |
| gr.HTML(""" |
| <div style="text-align: center; margin: 20px 0 10px 0;"> |
| <a href="https://www.humangen.ai" target="_blank" style="text-decoration: none;"> |
| <img src="https://img.shields.io/static/v1?label=๐ HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge" alt="HOME"> |
| </a> |
| </div> |
| """) |
| |
| gr.Markdown("# ๐ต SOMA MUSIC STUDIO ๐ถ", elem_classes="header-title") |
| |
| gr.Markdown(""" |
| <p style="text-align: center; font-family: 'Comic Neue', cursive; font-size: 1.1rem; font-weight: 700; color: #1F2937;"> |
| ๐ซ HeartMuLa-oss-3B + SOMA Multi-Agent = ์ต๊ณ ํ์ง AI ์์
์์ฑ ๐ซ |
| </p> |
| """) |
| |
| |
| with gr.Accordion("๐ Groq API Key (๊ฐ์ฌ ์์ฑ์ฉ)", open=False): |
| GROQ_KEY = os.environ.get("GROQ_API_KEY", "") |
| groq_key = gr.Textbox( |
| label="Groq API Key", |
| type="password", |
| value=GROQ_KEY, |
| placeholder="gsk_..." if not GROQ_KEY else "โ
Secret ๋ก๋๋จ", |
| interactive=not bool(GROQ_KEY) |
| ) |
| |
| with gr.Row(equal_height=False): |
| |
| with gr.Column(scale=1, min_width=400): |
| gr.Markdown("## ๐ LYRICS GENERATOR", elem_classes="section-title") |
| |
| theme_input = gr.Textbox( |
| label="๐ฏ ๋
ธ๋ ์ฃผ์ ", |
| placeholder="์: ์ด๋ณ ํ ์ฑ์ฅ, ๊ฟ์ ํฅํ ๋์ , ์ฌ๋์ ๊ณ ๋ฐฑ...", |
| lines=2 |
| ) |
| |
| with gr.Row(): |
| lyrics_genre = gr.Dropdown( |
| label="๐ธ ์ฅ๋ฅด", |
| choices=["K-Pop", "Pop", "R&B", "Hip-Hop", "Ballad", "Rock", |
| "EDM", "Jazz", "Folk", "Disco", "Cinematic"], |
| value="K-Pop" |
| ) |
| lyrics_mood = gr.Dropdown( |
| label="๐ซ ๋ถ์๊ธฐ", |
| choices=["Empowering", "Melancholic", "Joyful", "Romantic", |
| "Energetic", "Dreamy", "Nostalgic", "Peaceful", "Confident"], |
| value="Empowering" |
| ) |
| |
| with gr.Row(): |
| lyrics_language = gr.Dropdown( |
| label="๐ ์ธ์ด (Language)", |
| choices=["English", "Korean", "Chinese", "Japanese", "Spanish"], |
| value="English", |
| info="HeartMuLa ๊ณต์ ์ง์ 5๊ฐ ์ธ์ด" |
| ) |
| |
| lyrics_additional = gr.Textbox( |
| label="โจ ์ถ๊ฐ ์ง์ (์ ํ)", |
| placeholder="ํน๋ณ ์์ฒญ: ํน์ ๊ฐ์ , ์คํ์ผ ๋ฑ...", |
| lines=1 |
| ) |
| |
| with gr.Row(): |
| quick_btn = gr.Button("โก QUICK", variant="secondary") |
| soma_lyrics_btn = gr.Button("๐ง SOMA GENERATE", variant="primary") |
| |
| lyrics_status = gr.Textbox(label="๐ ์ํ", interactive=False, max_lines=1, elem_classes="status-box") |
| |
| with gr.Accordion("๐ SOMA ์์
๊ณผ์ ", open=False): |
| step1_out = gr.Textbox(label="1๏ธโฃ ์์ฌ๊ฐ", lines=3, interactive=False) |
| step2_out = gr.Textbox(label="2๏ธโฃ ํ๋ก๋์", lines=3, interactive=False) |
| step3_out = gr.Textbox(label="3๏ธโฃ ๊ฐ์ฑ", lines=3, interactive=False) |
| step4_out = gr.Textbox(label="4๏ธโฃ ์ต์ข
", lines=3, interactive=False) |
| |
| final_lyrics = gr.Textbox( |
| label="โ๏ธ ๊ฐ์ฌ (ํธ์ง ๊ฐ๋ฅ)", |
| lines=16, |
| value=EXAMPLE_LYRICS, |
| placeholder="๊ฐ์ฌ๊ฐ ์ฌ๊ธฐ ํ์๋ฉ๋๋ค..." |
| ) |
| |
| |
| with gr.Column(scale=1, min_width=400): |
| gr.Markdown("## ๐ต MUSIC GENERATOR", elem_classes="section-title") |
| |
| |
| gr.Markdown("### ๐ท๏ธ ํ๊ทธ ํ๋ฆฌ์
(ํด๋ฆญํ๋ฉด ์๋ ์
๋ ฅ)") |
| tag_preset = gr.Dropdown( |
| label="ํ๋ฆฌ์
์ ํ", |
| choices=list(EXAMPLE_TAG_PRESETS.keys()), |
| value=None |
| ) |
| |
| tags_input = gr.Textbox( |
| label="๐น ์คํ์ผ ํ๊ทธ (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์)", |
| value=EXAMPLE_TAGS, |
| placeholder="piano,happy,uplifting,pop", |
| lines=1, |
| elem_classes="tag-input" |
| ) |
| |
| with gr.Row(): |
| ai_tag_btn = gr.Button("๐ค AI ํ๊ทธ ์์ฑ", variant="secondary", size="sm") |
| |
| with gr.Accordion("โ๏ธ ์์ฑ ์ค์ ", open=True): |
| max_duration = gr.Slider( |
| minimum=30, maximum=240, value=120, step=10, |
| label="โฑ๏ธ ์ต๋ ๊ธธ์ด (์ด)", |
| info="30~240์ด" |
| ) |
| |
| with gr.Row(): |
| temperature = gr.Slider( |
| minimum=0.1, maximum=2.0, value=1.0, step=0.1, |
| label="๐ก๏ธ Temperature", |
| info="๋์์๋ก ์ฐฝ์์ " |
| ) |
| topk = gr.Slider( |
| minimum=1, maximum=100, value=50, step=1, |
| label="๐ฏ Top-K" |
| ) |
| |
| cfg_scale = gr.Slider( |
| minimum=1.0, maximum=3.0, value=1.5, step=0.1, |
| label="๐ CFG Scale", |
| info="Classifier-free guidance" |
| ) |
| |
| generate_btn = gr.Button( |
| "๐ถ GENERATE MUSIC!", |
| variant="primary", |
| size="lg", |
| elem_classes="generate-btn" |
| ) |
| |
| music_status = gr.Textbox(label="๐ ์ํ", interactive=False, max_lines=1, elem_classes="status-box") |
| audio_output = gr.Audio(label="๐ง ์์ฑ๋ ์์
", type="filepath") |
| |
| gr.Markdown(""" |
| ### ๐ก Tips |
| - **๊ตฌ์กฐ ํ๊ทธ:** [Intro], [Verse], [Prechorus], [Chorus], [Bridge], [Outro] |
| - **์คํ์ผ ํ๊ทธ:** piano,happy,uplifting,pop (์ฝค๋ง ๊ตฌ๋ถ, ๊ณต๋ฐฑ ์์!) |
| - **์ง์ ์ธ์ด:** ๐บ๐ธ EN | ๐ฐ๐ท KO | ๐จ๐ณ ZH | ๐ฏ๐ต JA | ๐ช๐ธ ES |
| - โ ๏ธ **๋ณด์ปฌ ํ์
(๋์ฃ, ๊ทธ๋ฃน ๋ฑ)์ ์ง์ํ์ง ์์ต๋๋ค!** |
| - **Temperature:** ๋์ผ๋ฉด ์ฐฝ์์ , ๋ฎ์ผ๋ฉด ์ผ๊ด์ฑ |
| """) |
| |
| |
| with gr.Accordion("๐ HeartMuLa ๊ฐ์ด๋", open=False): |
| gr.Markdown(f""" |
| ## โ ๏ธ HeartMuLa ์
๋ ฅ ํ์ (์ค์!) |
| |
| HeartMuLa๋ **2๊ฐ์ง ์
๋ ฅ๋ง** ๋ฐ์ต๋๋ค: |
| 1. **๊ฐ์ฌ (Lyrics)** - ๊ตฌ์กฐ ํ๊ทธ ํฌํจ |
| 2. **์คํ์ผ ํ๊ทธ (Tags)** - ์ฝค๋ง ๊ตฌ๋ถ |
| |
| โ **์ง์ํ์ง ์๋ ๊ฒ:** |
| - ๋ณด์ปฌ ํ์
์ ํ (๋์ฃ, ๊ทธ๋ฃน, ์ฝ๋ฌ์ค ๋ฑ) |
| - ๋ ํผ๋ฐ์ค ์ค๋์ค (์์ง ๋ฏธ์ง์) |
| - BPM/ํ
ํฌ ์ง์ ์ง์ |
| |
| --- |
| |
| ## ๐ ์ง์ ์ธ์ด (5๊ฐ) |
| | ์ธ์ด | ์ฝ๋ | ์ํ | |
| |------|------|------| |
| | ๐บ๐ธ English | EN | โ
| |
| | ๐ฐ๐ท Korean | KO | โ
| |
| | ๐จ๐ณ Chinese | ZH | โ
| |
| | ๐ฏ๐ต Japanese | JA | โ
| |
| | ๐ช๐ธ Spanish | ES | โ
| |
| |
| --- |
| |
| {HEARTMULA_TAG_GUIDE} |
| |
| --- |
| |
| {HEARTMULA_LYRICS_STRUCTURE} |
| |
| --- |
| |
| **Model:** [HeartMuLa-oss-3B](https://huggingface.co/HeartMuLa/HeartMuLa-oss-3B) | |
| **Paper:** [arXiv](https://arxiv.org/abs/2601.10547) | |
| **Code:** [GitHub](https://github.com/HeartMuLa/heartlib) |
| |
| *Licensed under CC BY-NC 4.0 (Non-Commercial)* |
| """) |
| |
| |
| |
| |
| tag_preset.change( |
| fn=load_tag_preset, |
| inputs=[tag_preset], |
| outputs=[tags_input] |
| ) |
| |
| |
| ai_tag_btn.click( |
| fn=generate_tags_ai, |
| inputs=[groq_key, lyrics_genre, lyrics_mood, tags_input], |
| outputs=[tags_input] |
| ) |
| |
| |
| quick_btn.click( |
| fn=quick_lyrics, |
| inputs=[groq_key, theme_input, lyrics_genre, lyrics_mood, lyrics_language, lyrics_additional], |
| outputs=[final_lyrics] |
| ) |
| |
| |
| soma_lyrics_btn.click( |
| fn=generate_lyrics_soma, |
| inputs=[groq_key, theme_input, lyrics_genre, lyrics_mood, lyrics_language, lyrics_additional], |
| outputs=[lyrics_status, step1_out, step2_out, step3_out, step4_out] |
| ).then( |
| fn=lambda x: x, |
| inputs=[step4_out], |
| outputs=[final_lyrics] |
| ) |
| |
| |
| generate_btn.click( |
| fn=generate_music_heartmula, |
| inputs=[final_lyrics, tags_input, max_duration, temperature, topk, cfg_scale], |
| outputs=[audio_output, music_status] |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |