File size: 9,434 Bytes
258fd02
 
 
0a1e140
8e684f6
f2f9818
3ef9463
2644f3e
3ef9463
989bff9
 
 
 
2b516e0
3ef9463
acfc75a
d658154
acfc75a
208580f
acfc75a
 
 
 
 
 
 
 
 
3162dea
acfc75a
258fd02
 
d01af32
258fd02
0a1e140
acfc75a
48275bf
 
 
 
d658154
 
48275bf
acfc75a
48275bf
258fd02
 
acfc75a
 
 
 
 
 
 
258fd02
989bff9
 
 
acfc75a
989bff9
 
acfc75a
989bff9
 
 
 
acfc75a
45a12c7
ef01ecd
 
acfc75a
 
 
 
 
 
af26727
ef01ecd
0a1e140
acfc75a
d658154
3c8f8cf
acfc75a
f7c5fc0
 
acfc75a
3c8f8cf
af26727
acfc75a
3c8f8cf
 
 
acfc75a
3c8f8cf
f7c5fc0
acfc75a
af26727
 
3c8f8cf
 
 
 
 
 
 
 
 
 
 
 
 
acfc75a
af26727
 
acfc75a
3c8f8cf
 
258fd02
d658154
 
 
 
e7ab0ec
 
8e684f6
acfc75a
8e684f6
258fd02
acfc75a
 
 
8e684f6
 
258fd02
 
3c8f8cf
d658154
8e684f6
d658154
8e684f6
 
258fd02
acfc75a
258fd02
 
989bff9
 
258fd02
d658154
acfc75a
 
 
 
258fd02
 
 
 
 
 
d658154
258fd02
acfc75a
258fd02
 
 
48275bf
 
 
acfc75a
48275bf
acfc75a
48275bf
d658154
 
 
acfc75a
48275bf
d658154
 
258fd02
eb8bfb7
 
 
acfc75a
eb8bfb7
d658154
 
acfc75a
 
 
3779445
acfc75a
258fd02
 
989bff9
3c8f8cf
258fd02
 
 
c8c0ef5
3779445
 
258fd02
acfc75a
258fd02
3ef9463
acfc75a
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import gradio as gr
import json
from datetime import datetime
import yaml
import time
import re
import os
import os.path as op
import torch
import soundfile as sf
import numpy as np
import tempfile

from download import download_model

# 🎯 خطوة السيادة الأولى: توجيه المحرك لتحميل أوزانكِ الخاصة بدلاً من المستودع العام
APP_DIR = op.dirname(op.abspath(__file__))
MODEL_ID = "Novix/SongGenerationtwo"  # مستودع أوزانكِ السيادية

print(f"⏳ [Novix Core] جاري سحب الأوزان السيادية من المستودع: {MODEL_ID}...")
try:
    # تحميل الأوزان مباشرة إلى البيئة المحلية للـ Space
    download_model(APP_DIR, repo_id=MODEL_ID, revision="main")
    print("✅ تم تحميل الأوزان السيادية لـ Novix بنجاح.")
except Exception as e:
    print(f"⚠️ تنبيه أثناء تحميل الأوزان: {e}. سيتم الاعتماد على الأوزان المحلية إن وجدت.")

# تهيئة وإقلاع المحرك من الفئة الأصلية المستقرة
from levo_inference import LeVoInference
MODEL = None

EXAMPLE_LYRICS = """
[intro-medium]

[verse]
随风去流浪
我不想停留原地
原地只有无尽循环
不再规划人生
不再遵循地图

[chorus]
让我随风去流浪
邂逅未知的自己
生命最绚烂的章节
""".strip()

# قراءة قاموس التوكنز الصوتي الأصلي للموديل
vocab_path = op.join(APP_DIR, 'conf/vocab.yaml')
if op.exists(vocab_path):
    with open(vocab_path, 'r', encoding='utf-8') as file:
        STRUCTS = yaml.safe_load(file)
else:
    STRUCTS = ['[intro]', '[intro-short]', '[intro-medium]', '[verse]', '[chorus]', '[bridge]', '[inst]', '[inst-short]', '[inst-medium]', '[outro]', '[outro-short]', '[outro-medium]']

def save_as_flac(sample_rate, audio_data):
    if isinstance(audio_data, tuple):
        sample_rate, audio_data = audio_data
        
    if audio_data.dtype == np.float64:
        audio_data = audio_data.astype(np.float32)
        
    temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".flac")
    sf.write(temp_file, audio_data, sample_rate, format='FLAC')
    return temp_file.name

# دالة التوليد الأصلية بعد ربطها بـ Novix Core
def generate_song(lyric, description=None, prompt_audio=None, genre=None, cfg_coef=None, temperature=0.1, top_k=-1, gen_type="mixed", progress=gr.Progress(track_tqdm=True)):
    global MODEL
    global STRUCTS
    
    if MODEL is None:
        return None, json.dumps({"error": "المحرك لم يتم تحميله في الذاكرة بعد. يرجى إعادة تحديث الصفحة أو مراجعة السجلات."})
        
    params = {'cfg_coef': cfg_coef, 'temperature': temperature, 'top_k': top_k}
    params = {k: v for k, v in params.items() if v is not None}
    vocal_structs = ['[verse]', '[chorus]', '[bridge]']
    sample_rate = MODEL.cfg.sample_rate
    
    # تنسيق وتطهير الكلمات والمقاطع الهيكلية
    lyric = lyric.replace("[intro]", "[intro-short]").replace("[inst]", "[inst-short]").replace("[outro]", "[outro-short]")
    paragraphs = [p.strip() for p in lyric.strip().split('\n\n') if p.strip()]
    
    if len(paragraphs) < 1:
        return None, json.dumps("Lyrics can not be left blank")
        
    paragraphs_norm = []
    vocal_flag = False
    
    for para in paragraphs:
        lines = para.splitlines()
        struct_tag = lines[0].strip().lower()
        
        if struct_tag not in STRUCTS:
            return None, json.dumps(f"Segments should start with a structure tag in {STRUCTS}")
            
        if struct_tag in vocal_structs:
            vocal_flag = True
            if len(lines) < 2 or not [line.strip() for line in lines[1:] if line.strip()]:
                return None, json.dumps("The following segments require lyrics: [verse], [chorus], [bridge]")
            else:
                new_para_list = []
                for line in lines[1:]:
                    new_para_list.append(re.sub(r"[^\w\s\[\]\-\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af\u00c0-\u017f]", "", line))
                new_para_str = f"{struct_tag} {'.'.join(new_para_list)}"
        else:
            if len(lines) > 1:
                return None, json.dumps("The following segments should not contain lyrics: [intro], [intro-short], [intro-medium], [inst], [inst-short], [inst-medium], [outro], [outro-short], [outro-medium]")
            else:
                new_para_str = struct_tag
        paragraphs_norm.append(new_para_str)
        
    if not vocal_flag:
        return None, json.dumps(f"The lyric must contain at least one of the following structures: {vocal_structs}")
        
    lyric_norm = " ; ".join(paragraphs_norm)

    if prompt_audio is not None:
        genre = None
        description = None
    elif description is not None and description != "":
        genre = None
        if description[-1] != ".":
            description = description + "."

    progress(0.0, "⚡ [Novix Core] التوليد مستمر الآن...")
    start = time.time()
    
    # تشغيل المصفوفات الحقيقية للأوزان المستقلة
    prompt_path = op.join(APP_DIR, "tools/new_prompt.pt")
    audio_data = MODEL(lyric_norm, description, prompt_audio, genre, prompt_path, gen_type, params).cpu().permute(1, 0).float().numpy()

    end = time.time()
    
    input_config = {
        "lyric": lyric_norm,
        "genre": genre,
        "prompt_audio": prompt_audio,
        "description": description,
        "params": params,
        "inference_duration": end - start,
        "timestamp": datetime.now().isoformat(),
        "engine": "Novix Sovereign Studio (Independent Mode)"
    }
    
    filepath = save_as_flac(sample_rate, audio_data)
    return filepath, json.dumps(input_config, indent=2)


# بناء الواجهة الاحترافية الكبرى لـ Gradio
with gr.Blocks(title="Novix Sovereign Studio Pro") as demo:
    gr.Markdown("# 🎵 استوديو Novix المستقل والمملوك لك بالكامل 100%")
    gr.Markdown("🛡️ تم فك الارتباط من خوادم الشركات وتوجيه النواة لأوزانكِ الخاصة للإنتاج والربح الحر.")
    
    with gr.Row():
        with gr.Column():
            lyric = gr.Textbox(
                label="Lyrics",
                lines=5,
                max_lines=15,
                value=EXAMPLE_LYRICS,
                info="قوالب المقاطع الصوتية المدعومة: [intro], [verse], [chorus], [bridge], [inst], [outro]"
            )

            with gr.Tabs(elem_id="extra-tabs"):
                with gr.Tab("Genre Select"):
                    genre = gr.Radio(
                        choices=["Auto", "Pop", "Latin", "Rock", "Electronic", "Metal", "Country", "R&B/Soul", "Ballad", "Jazz", "World", "Hip-Hop", "Funk", "Soundtrack"],
                        label="Genre Select (Optional)",
                        value="Auto",
                        interactive=True
                    )
                with gr.Tab("Text Prompt"):
                    description = gr.Textbox(
                        label="Song Description (Optional)",
                        info="اكتبي مواصفات الصوت بالأرقام أو الإنجليزية (مثال: female, sad pop, piano).",
                        placeholder="female, rock, motivational, electric guitar, bass guitar, drum kit.",
                        lines=1,
                        max_lines=2
                    )
                with gr.Tab("Audio Prompt"):
                    prompt_audio = gr.Audio(
                        label="Prompt Audio (Optional)",
                        type="filepath"
                    )

            with gr.Accordion("Advanced Config", open=False):
                cfg_coef = gr.Slider(label="CFG Coefficient", minimum=0.1, maximum=3.0, step=0.1, value=1.8, interactive=True)
                temperature = gr.Slider(label="Temperature", minimum=0.1, maximum=2.0, step=0.1, value=0.8, interactive=True)
                
            with gr.Row():
                generate_btn = gr.Button("Generate Song (Sovereign Mode)", variant="primary")
        
        with gr.Column():
            output_audio = gr.Audio(label="Generated Song", type="filepath")
            output_json = gr.JSON(label="Generated Info")
    
    generate_btn.click(
        fn=generate_song,
        inputs=[lyric, description, prompt_audio, genre, cfg_coef, temperature, gr.State(5000)],
        outputs=[output_audio, output_json]
    )

# تشغيل الإقلاع المستقل للنواة
if __name__ == "__main__":
    torch.set_num_threads(1)
    ckpt_path = op.join(APP_DIR, "ckpt")
    
    # التأكد من وجود مجلد الأوزان وإقلاع النموذج فوراً
    if not op.exists(ckpt_path):
        os.makedirs(ckpt_path, exist_ok=True)
        
    print("🧠 جاري صهر وبناء بيئة الاستدلال الصوتي المستقل...")
    MODEL = LeVoInference(ckpt_path)
    print("✅ النصر الكلي! الاستوديو شغال أوفلاين وجاهز لاستقبال ضربة زر التوليد.")
    
    demo.launch(server_name="0.0.0.0", server_port=7860)