Gamahea commited on
Commit
d5d943f
·
1 Parent(s): 12fedcc

LEMM v1.0.0 - Rename and add HF repo storage persistence

Browse files

MAJOR UPDATE:
- Renamed to 'LEMM - Let Everyone Make Music' v1.0.0 (beta)
- Header updated with new branding and description
- Training data now persists in HuggingFace dataset repo

Critical Fixes:
- Fixed mastering syntax error (line 439)
- Improved LyricMind model loading with retry logic
- Better placeholder warnings when model not loaded

HuggingFace Repo Integration:
- Added HFStorageService for Gamahea/lemm-dataset repo
- Auto-sync LoRAs and datasets from repo on startup
- Auto-upload trained LoRAs after training
- Auto-upload prepared datasets after preparation
- Data persists across Space rebuilds

Header Changes:
- Title: LEMM - Let Everyone Make Music
- Version 1.0.0 (beta) displayed
- New description: Advanced AI music generator with training, EQ, Mastering, Super Resolution
- Removed deprecated duration tip

Storage Flow:
- Startup: Download existing LoRAs/datasets from HF repo
- After training: Upload LoRA to repo (survives rebuilds)
- After prep: Upload dataset to repo (survives rebuilds)

Dependencies:
- Added huggingface-hub>=0.20.0

app.py CHANGED
@@ -7,6 +7,7 @@ import sys
7
  import gradio as gr
8
  import logging
9
  from pathlib import Path
 
10
  import shutil
11
  import subprocess
12
  import json
@@ -52,6 +53,7 @@ try:
52
  from services.lyricmind_service import LyricMindService
53
  from services.timeline_service import TimelineService
54
  from services.export_service import ExportService
 
55
  from config.settings import Config
56
  from utils.prompt_analyzer import PromptAnalyzer
57
  except ImportError as e:
@@ -72,6 +74,15 @@ os.makedirs("logs", exist_ok=True)
72
  timeline_service = TimelineService()
73
  export_service = ExportService()
74
 
 
 
 
 
 
 
 
 
 
75
  # Lazy-load AI services (heavy models)
76
  diffrhythm_service = None
77
  lyricmind_service = None
@@ -438,6 +449,8 @@ def export_timeline(filename: str, export_format: str, timeline_state: dict, pro
438
  def get_timeline_playback(timeline_state: dict):
439
  """Get merged timeline audio for playback"""
440
  try:
 
 
441
  # Restore timeline from state
442
  if timeline_state and 'clips' in timeline_state:
443
  timeline_service.clips = []
@@ -445,11 +458,15 @@ def get_timeline_playback(timeline_state: dict):
445
  from models.schemas import TimelineClip
446
  clip = TimelineClip(**clip_data)
447
  timeline_service.clips.append(clip)
448
- logger.info(f"[STATE] Restored {len(timeline_service.clips)} clips for playback")
 
 
449
 
450
  clips = timeline_service.get_all_clips()
 
451
 
452
  if not clips:
 
453
  return None
454
 
455
  # Use export service to merge clips
@@ -459,7 +476,7 @@ def get_timeline_playback(timeline_state: dict):
459
  export_format="wav"
460
  )
461
 
462
- logger.info(f"Timeline playback ready: {output_path}")
463
  return output_path
464
 
465
  except Exception as e:
@@ -1165,6 +1182,16 @@ def prepare_datasets_for_training(selected_datasets, max_samples_per_dataset):
1165
  if success_count > 0:
1166
  status_messages.append(f"\n✅ Datasets are now ready for LoRA training!")
1167
  status_messages.append(f"💡 Go to 'Training Configuration' tab to start training")
 
 
 
 
 
 
 
 
 
 
1168
 
1169
  return "\n".join(status_messages)
1170
 
@@ -1301,23 +1328,6 @@ def refresh_dataset_list():
1301
  logger.error(f"Failed to refresh datasets: {e}")
1302
  return gr.Dropdown(choices=["Error loading datasets"])
1303
 
1304
- def refresh_lora_list():
1305
- """Refresh list of available LoRA adapters"""
1306
- try:
1307
- from backend.services.lora_training_service import LoRATrainingService
1308
- lora_service = LoRATrainingService()
1309
-
1310
- loras = lora_service.list_loras()
1311
-
1312
- if not loras:
1313
- return gr.Dropdown(choices=["No LoRA adapters found"], value=None)
1314
-
1315
- return gr.Dropdown(choices=loras, value=loras[0] if loras else None)
1316
-
1317
- except Exception as e:
1318
- logger.error(f"Failed to refresh LoRAs: {e}")
1319
- return gr.Dropdown(choices=["Error loading LoRAs"], value=None)
1320
-
1321
  def start_lora_training(lora_name, dataset, batch_size, learning_rate, num_epochs, lora_rank, lora_alpha):
1322
  """Start LoRA training"""
1323
  try:
@@ -1367,6 +1377,16 @@ def start_lora_training(lora_name, dataset, batch_size, learning_rate, num_epoch
1367
  progress += f"\n✅ Training complete!\nFinal validation loss: {results['final_val_loss']:.4f}"
1368
  log += f"\n\nTraining Results:\n{json.dumps(results, indent=2)}"
1369
 
 
 
 
 
 
 
 
 
 
 
1370
  return progress, log
1371
 
1372
  except Exception as e:
@@ -1407,11 +1427,12 @@ def refresh_lora_list():
1407
  ])
1408
  lora_names.append(adapter.get('name', ''))
1409
 
1410
- return table_data, gr.Dropdown(choices=lora_names)
 
1411
 
1412
  except Exception as e:
1413
  logger.error(f"Failed to refresh LoRA list: {e}")
1414
- return [], gr.Dropdown(choices=[])
1415
 
1416
  def delete_lora(lora_name):
1417
  """Delete selected LoRA adapter"""
@@ -1433,19 +1454,124 @@ def delete_lora(lora_name):
1433
  logger.error(f"Failed to delete LoRA: {e}")
1434
  return f"❌ Error: {str(e)}"
1435
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1436
  # Create Gradio interface
1437
  with gr.Blocks(
1438
- title="🎵 Music Generation Studio",
1439
  theme=gr.themes.Soft(primary_hue="purple", secondary_hue="pink")
1440
  ) as app:
1441
 
1442
  gr.Markdown(
1443
  """
1444
- # 🎵 Music Generation Studio
 
1445
 
1446
- Create AI-powered music with DiffRhythm2 and LyricMind AI
1447
-
1448
- 💡 **Tip**: Start with 10-20 second clips for faster generation with ZeroGPU
1449
  """
1450
  )
1451
 
@@ -1732,6 +1858,10 @@ with gr.Blocks(
1732
  fn=generate_music,
1733
  inputs=[prompt_input, lyrics_input, lyrics_mode, position_input, context_length_input, timeline_state],
1734
  outputs=[gen_status, timeline_display, audio_output, timeline_state]
 
 
 
 
1735
  )
1736
 
1737
  remove_btn.click(
@@ -1948,6 +2078,27 @@ with gr.Blocks(
1948
 
1949
  prepare_user_dataset_btn = gr.Button("📦 Prepare Training Dataset", variant="primary")
1950
  user_prepare_status = gr.Textbox(label="Preparation Status", lines=2, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1951
 
1952
  # Tab 3: Training Configuration
1953
  with gr.Tab("⚙️ Training Configuration"):
@@ -1967,6 +2118,21 @@ with gr.Blocks(
1967
 
1968
  refresh_datasets_btn = gr.Button("🔄 Refresh Datasets", size="sm")
1969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1970
  gr.Markdown("#### Hyperparameters")
1971
 
1972
  with gr.Row():
@@ -2036,25 +2202,47 @@ with gr.Blocks(
2036
 
2037
  # Tab 4: Manage LoRA Adapters
2038
  with gr.Tab("📂 Manage LoRA Adapters"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2039
  gr.Markdown("### Installed LoRA Adapters")
2040
 
2041
  lora_list = gr.Dataframe(
2042
  headers=["Name", "Created", "Training Steps", "Type"],
2043
  datatype=["str", "str", "number", "str"],
2044
  row_count=10,
2045
- label="Available LoRA Adapters"
 
2046
  )
2047
 
2048
  with gr.Row():
2049
  refresh_lora_btn = gr.Button("🔄 Refresh List", size="sm")
2050
- selected_lora = gr.Dropdown(
2051
- choices=[],
2052
- label="Select LoRA",
2053
- scale=2
2054
- )
2055
- delete_lora_btn = gr.Button("🗑️ Delete LoRA", variant="stop", size="sm")
2056
 
2057
- lora_management_status = gr.Textbox(label="Status", lines=1, interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
2058
 
2059
  gr.Markdown("---")
2060
  gr.Markdown(
@@ -2171,17 +2359,63 @@ with gr.Blocks(
2171
  refresh_lora_btn.click(
2172
  fn=refresh_lora_list,
2173
  inputs=[],
2174
- outputs=[lora_list, selected_lora]
2175
  )
2176
 
2177
  delete_lora_btn.click(
2178
  fn=delete_lora,
2179
- inputs=[selected_lora],
2180
- outputs=[lora_management_status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2181
  ).then(
2182
  fn=refresh_lora_list,
2183
  inputs=[],
2184
- outputs=[lora_list, selected_lora]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2185
  )
2186
 
2187
  # Help section
 
7
  import gradio as gr
8
  import logging
9
  from pathlib import Path
10
+ from datetime import datetime
11
  import shutil
12
  import subprocess
13
  import json
 
53
  from services.lyricmind_service import LyricMindService
54
  from services.timeline_service import TimelineService
55
  from services.export_service import ExportService
56
+ from services.hf_storage_service import HFStorageService
57
  from config.settings import Config
58
  from utils.prompt_analyzer import PromptAnalyzer
59
  except ImportError as e:
 
74
  timeline_service = TimelineService()
75
  export_service = ExportService()
76
 
77
+ # Initialize HF storage and sync training data
78
+ hf_storage = HFStorageService(repo_id="Gamahea/lemm-dataset")
79
+ logger.info("🔄 Syncing training data from HuggingFace repo...")
80
+ sync_result = hf_storage.sync_on_startup(
81
+ loras_dir=Path("models/loras"),
82
+ datasets_dir=Path("training_data")
83
+ )
84
+ logger.info(f"✅ Synced {len(sync_result['loras'])} LoRAs and {len(sync_result['datasets'])} datasets")
85
+
86
  # Lazy-load AI services (heavy models)
87
  diffrhythm_service = None
88
  lyricmind_service = None
 
449
  def get_timeline_playback(timeline_state: dict):
450
  """Get merged timeline audio for playback"""
451
  try:
452
+ logger.info(f"[PLAYBACK] get_timeline_playback called with state: {timeline_state is not None}")
453
+
454
  # Restore timeline from state
455
  if timeline_state and 'clips' in timeline_state:
456
  timeline_service.clips = []
 
458
  from models.schemas import TimelineClip
459
  clip = TimelineClip(**clip_data)
460
  timeline_service.clips.append(clip)
461
+ logger.info(f"[PLAYBACK] Restored {len(timeline_service.clips)} clips from state")
462
+ else:
463
+ logger.warning(f"[PLAYBACK] No valid timeline_state provided: {timeline_state}")
464
 
465
  clips = timeline_service.get_all_clips()
466
+ logger.info(f"[PLAYBACK] Total clips in timeline: {len(clips)}")
467
 
468
  if not clips:
469
+ logger.warning("[PLAYBACK] No clips available for playback")
470
  return None
471
 
472
  # Use export service to merge clips
 
476
  export_format="wav"
477
  )
478
 
479
+ logger.info(f"[PLAYBACK] Timeline playback ready: {output_path}")
480
  return output_path
481
 
482
  except Exception as e:
 
1182
  if success_count > 0:
1183
  status_messages.append(f"\n✅ Datasets are now ready for LoRA training!")
1184
  status_messages.append(f"💡 Go to 'Training Configuration' tab to start training")
1185
+
1186
+ # Upload prepared datasets to HF repo
1187
+ status_messages.append(f"\n📤 Uploading prepared datasets to HuggingFace repo...")
1188
+ upload_count = 0
1189
+ for dataset_key in datasets_to_process:
1190
+ dataset_dir = Path("training_data") / dataset_key
1191
+ if dataset_dir.exists():
1192
+ if hf_storage.upload_dataset(dataset_dir):
1193
+ upload_count += 1
1194
+ status_messages.append(f"✅ Uploaded {upload_count} dataset(s) to repo")
1195
 
1196
  return "\n".join(status_messages)
1197
 
 
1328
  logger.error(f"Failed to refresh datasets: {e}")
1329
  return gr.Dropdown(choices=["Error loading datasets"])
1330
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1331
  def start_lora_training(lora_name, dataset, batch_size, learning_rate, num_epochs, lora_rank, lora_alpha):
1332
  """Start LoRA training"""
1333
  try:
 
1377
  progress += f"\n✅ Training complete!\nFinal validation loss: {results['final_val_loss']:.4f}"
1378
  log += f"\n\nTraining Results:\n{json.dumps(results, indent=2)}"
1379
 
1380
+ # Upload trained LoRA to HF repo
1381
+ progress += "\n\n📤 Uploading LoRA to HuggingFace repo..."
1382
+ lora_dir = Path("models/loras") / lora_name
1383
+ if lora_dir.exists():
1384
+ upload_success = hf_storage.upload_lora(lora_dir)
1385
+ if upload_success:
1386
+ progress += "\n✅ LoRA uploaded to repo successfully!"
1387
+ else:
1388
+ progress += "\n⚠️ LoRA trained but upload failed (saved locally)"
1389
+
1390
  return progress, log
1391
 
1392
  except Exception as e:
 
1427
  ])
1428
  lora_names.append(adapter.get('name', ''))
1429
 
1430
+ # Return table data and update both dropdowns (action dropdown and base_lora dropdown)
1431
+ return table_data, gr.Dropdown(choices=lora_names), gr.Dropdown(choices=lora_names)
1432
 
1433
  except Exception as e:
1434
  logger.error(f"Failed to refresh LoRA list: {e}")
1435
+ return [], gr.Dropdown(choices=[]), gr.Dropdown(choices=[])
1436
 
1437
  def delete_lora(lora_name):
1438
  """Delete selected LoRA adapter"""
 
1454
  logger.error(f"Failed to delete LoRA: {e}")
1455
  return f"❌ Error: {str(e)}"
1456
 
1457
+ def download_lora(lora_name):
1458
+ """Export LoRA adapter as zip file for download"""
1459
+ try:
1460
+ if not lora_name:
1461
+ return None, "❌ No LoRA selected"
1462
+
1463
+ from backend.services.lora_training_service import LoRATrainingService
1464
+ lora_service = LoRATrainingService()
1465
+
1466
+ zip_path = lora_service.export_lora_adapter(lora_name)
1467
+
1468
+ if zip_path:
1469
+ # Return the file path for Gradio to handle the download
1470
+ return zip_path, f"✅ Ready to download: {lora_name}.zip (click the file above to download)"
1471
+ else:
1472
+ return None, f"❌ Failed to export: {lora_name}"
1473
+
1474
+ except Exception as e:
1475
+ logger.error(f"Failed to export LoRA: {e}")
1476
+ return None, f"❌ Error: {str(e)}"
1477
+
1478
+ def upload_lora(zip_file):
1479
+ """Import LoRA adapter from zip file"""
1480
+ try:
1481
+ if not zip_file:
1482
+ return "❌ No file selected"
1483
+
1484
+ from backend.services.lora_training_service import LoRATrainingService
1485
+ lora_service = LoRATrainingService()
1486
+
1487
+ lora_name = lora_service.import_lora_adapter(zip_file)
1488
+
1489
+ if lora_name:
1490
+ return f"✅ Imported LoRA adapter: {lora_name}"
1491
+ else:
1492
+ return "❌ Failed to import LoRA"
1493
+
1494
+ except Exception as e:
1495
+ logger.error(f"Failed to import LoRA: {e}")
1496
+ return f"❌ Error: {str(e)}"
1497
+
1498
+ def toggle_base_lora(use_existing):
1499
+ """Toggle visibility of base LoRA adapter dropdown"""
1500
+ return gr.Dropdown(visible=use_existing)
1501
+
1502
+ def export_dataset(dataset_key):
1503
+ """Export prepared dataset as zip file"""
1504
+ try:
1505
+ if not dataset_key:
1506
+ return None, "❌ No dataset selected"
1507
+
1508
+ from backend.services.dataset_service import DatasetService
1509
+ dataset_service = DatasetService()
1510
+
1511
+ zip_path = dataset_service.export_prepared_dataset(dataset_key)
1512
+
1513
+ if zip_path:
1514
+ return zip_path, f"✅ Dataset exported: {dataset_key}.zip"
1515
+ else:
1516
+ return None, f"❌ Failed to export: {dataset_key}"
1517
+
1518
+ except Exception as e:
1519
+ logger.error(f"Failed to export dataset: {e}")
1520
+ return None, f"❌ Error: {str(e)}"
1521
+
1522
+ def import_dataset(zip_file):
1523
+ """Import prepared dataset from zip file"""
1524
+ try:
1525
+ if not zip_file:
1526
+ return "❌ No file selected"
1527
+
1528
+ from backend.services.dataset_service import DatasetService
1529
+ dataset_service = DatasetService()
1530
+
1531
+ dataset_key = dataset_service.import_prepared_dataset(zip_file)
1532
+
1533
+ if dataset_key:
1534
+ return f"✅ Imported dataset: {dataset_key}"
1535
+ else:
1536
+ return "❌ Failed to import dataset"
1537
+
1538
+ except Exception as e:
1539
+ logger.error(f"Failed to import dataset: {e}")
1540
+ return f"❌ Error: {str(e)}"
1541
+
1542
+ def refresh_export_dataset_list():
1543
+ """Refresh list of datasets available for export"""
1544
+ try:
1545
+ from backend.services.dataset_service import DatasetService
1546
+ dataset_service = DatasetService()
1547
+
1548
+ # Get all available datasets (both HF and user)
1549
+ all_datasets = dataset_service.get_all_available_datasets()
1550
+
1551
+ # Filter to only prepared datasets
1552
+ prepared = []
1553
+ for key, info in all_datasets.items():
1554
+ if info.get('prepared', False):
1555
+ prepared.append(key)
1556
+
1557
+ return gr.Dropdown(choices=prepared)
1558
+
1559
+ except Exception as e:
1560
+ logger.error(f"Failed to refresh export list: {e}")
1561
+ return gr.Dropdown(choices=[])
1562
+
1563
  # Create Gradio interface
1564
  with gr.Blocks(
1565
+ title="LEMM - Let Everyone Make Music v1.0.0 (beta)",
1566
  theme=gr.themes.Soft(primary_hue="purple", secondary_hue="pink")
1567
  ) as app:
1568
 
1569
  gr.Markdown(
1570
  """
1571
+ # 🎵 LEMM - Let Everyone Make Music
1572
+ **Version 1.0.0 (beta)**
1573
 
1574
+ Advanced AI music generator with built-in training, EQ, Mastering, and Super Resolution. Training data is stored safely on a separate repo for download / reuse.
 
 
1575
  """
1576
  )
1577
 
 
1858
  fn=generate_music,
1859
  inputs=[prompt_input, lyrics_input, lyrics_mode, position_input, context_length_input, timeline_state],
1860
  outputs=[gen_status, timeline_display, audio_output, timeline_state]
1861
+ ).then(
1862
+ fn=get_timeline_playback,
1863
+ inputs=[timeline_state],
1864
+ outputs=[timeline_playback]
1865
  )
1866
 
1867
  remove_btn.click(
 
2078
 
2079
  prepare_user_dataset_btn = gr.Button("📦 Prepare Training Dataset", variant="primary")
2080
  user_prepare_status = gr.Textbox(label="Preparation Status", lines=2, interactive=False)
2081
+
2082
+ gr.Markdown("---")
2083
+ gr.Markdown("### 📤 Dataset Import/Export")
2084
+
2085
+ with gr.Row():
2086
+ dataset_to_export = gr.Dropdown(
2087
+ choices=[],
2088
+ label="Select Dataset to Export",
2089
+ info="Download prepared datasets"
2090
+ )
2091
+ export_dataset_btn = gr.Button("⬇️ Export Dataset", variant="primary", size="sm")
2092
+
2093
+ with gr.Row():
2094
+ import_dataset_file = gr.File(
2095
+ label="Import Dataset (.zip)",
2096
+ file_types=[".zip"],
2097
+ type="filepath"
2098
+ )
2099
+
2100
+ dataset_download_file = gr.File(label="Downloaded Dataset", visible=True, interactive=False)
2101
+ dataset_export_status = gr.Textbox(label="Export/Import Status", lines=2, interactive=False)
2102
 
2103
  # Tab 3: Training Configuration
2104
  with gr.Tab("⚙️ Training Configuration"):
 
2118
 
2119
  refresh_datasets_btn = gr.Button("🔄 Refresh Datasets", size="sm")
2120
 
2121
+ gr.Markdown("#### Fine-tune Existing LoRA (Optional)")
2122
+
2123
+ use_existing_lora = gr.Checkbox(
2124
+ label="Continue training from existing LoRA",
2125
+ value=False,
2126
+ info="Start from a pre-trained LoRA adapter instead of from scratch"
2127
+ )
2128
+
2129
+ base_lora_adapter = gr.Dropdown(
2130
+ choices=[],
2131
+ label="Base LoRA Adapter",
2132
+ info="Select LoRA to continue training from",
2133
+ visible=False
2134
+ )
2135
+
2136
  gr.Markdown("#### Hyperparameters")
2137
 
2138
  with gr.Row():
 
2202
 
2203
  # Tab 4: Manage LoRA Adapters
2204
  with gr.Tab("📂 Manage LoRA Adapters"):
2205
+ gr.Markdown("### Upload New LoRA Adapter")
2206
+
2207
+ with gr.Row():
2208
+ upload_lora_file = gr.File(
2209
+ label="📤 Upload LoRA (.zip)",
2210
+ file_types=[".zip"],
2211
+ type="filepath",
2212
+ scale=3
2213
+ )
2214
+ upload_lora_btn = gr.Button("Upload", variant="primary", size="sm")
2215
+
2216
+ upload_lora_status = gr.Textbox(label="Upload Status", lines=1, interactive=False)
2217
+
2218
+ gr.Markdown("---")
2219
  gr.Markdown("### Installed LoRA Adapters")
2220
 
2221
  lora_list = gr.Dataframe(
2222
  headers=["Name", "Created", "Training Steps", "Type"],
2223
  datatype=["str", "str", "number", "str"],
2224
  row_count=10,
2225
+ label="Available LoRA Adapters",
2226
+ interactive=False
2227
  )
2228
 
2229
  with gr.Row():
2230
  refresh_lora_btn = gr.Button("🔄 Refresh List", size="sm")
 
 
 
 
 
 
2231
 
2232
+ gr.Markdown("### Actions on Selected LoRA")
2233
+
2234
+ selected_lora_for_action = gr.Dropdown(
2235
+ choices=[],
2236
+ label="Select LoRA Adapter",
2237
+ info="Choose a LoRA to download or delete"
2238
+ )
2239
+
2240
+ with gr.Row():
2241
+ download_lora_btn = gr.Button("⬇️ Download LoRA", variant="primary", size="lg", scale=1)
2242
+ delete_lora_btn = gr.Button("🗑️ Delete LoRA", variant="stop", size="lg", scale=1)
2243
+
2244
+ lora_download_file = gr.File(label="Downloaded LoRA", interactive=False)
2245
+ lora_action_status = gr.Textbox(label="Action Status", lines=1, interactive=False)
2246
 
2247
  gr.Markdown("---")
2248
  gr.Markdown(
 
2359
  refresh_lora_btn.click(
2360
  fn=refresh_lora_list,
2361
  inputs=[],
2362
+ outputs=[lora_list, selected_lora_for_action, base_lora_adapter]
2363
  )
2364
 
2365
  delete_lora_btn.click(
2366
  fn=delete_lora,
2367
+ inputs=[selected_lora_for_action],
2368
+ outputs=[lora_action_status]
2369
+ ).then(
2370
+ fn=refresh_lora_list,
2371
+ inputs=[],
2372
+ outputs=[lora_list, selected_lora_for_action, base_lora_adapter]
2373
+ )
2374
+
2375
+ download_lora_btn.click(
2376
+ fn=download_lora,
2377
+ inputs=[selected_lora_for_action],
2378
+ outputs=[lora_download_file, lora_action_status]
2379
+ )
2380
+
2381
+ upload_lora_btn.click(
2382
+ fn=upload_lora,
2383
+ inputs=[upload_lora_file],
2384
+ outputs=[upload_lora_status]
2385
  ).then(
2386
  fn=refresh_lora_list,
2387
  inputs=[],
2388
+ outputs=[lora_list, selected_lora_for_action, base_lora_adapter]
2389
+ )
2390
+
2391
+ use_existing_lora.change(
2392
+ fn=toggle_base_lora,
2393
+ inputs=[use_existing_lora],
2394
+ outputs=[base_lora_adapter]
2395
+ )
2396
+
2397
+ export_dataset_btn.click(
2398
+ fn=export_dataset,
2399
+ inputs=[dataset_to_export],
2400
+ outputs=[dataset_download_file, dataset_export_status]
2401
+ )
2402
+
2403
+ import_dataset_file.change(
2404
+ fn=import_dataset,
2405
+ inputs=[import_dataset_file],
2406
+ outputs=[dataset_export_status]
2407
+ ).then(
2408
+ fn=refresh_dataset_status,
2409
+ inputs=[],
2410
+ outputs=[vocal_datasets, symbolic_datasets, prepare_datasets_selector]
2411
+ ).then(
2412
+ fn=refresh_dataset_list,
2413
+ inputs=[],
2414
+ outputs=[selected_dataset]
2415
+ ).then(
2416
+ fn=refresh_export_dataset_list,
2417
+ inputs=[],
2418
+ outputs=[dataset_to_export]
2419
  )
2420
 
2421
  # Help section
backend/services/hf_storage_service.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ HuggingFace Dataset Repository Storage Service
3
+ Stores and retrieves training data and LoRA adapters from HF dataset repo
4
+ """
5
+ import os
6
+ import logging
7
+ from pathlib import Path
8
+ from typing import List, Dict, Optional
9
+ import shutil
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class HFStorageService:
14
+ """Service for storing/retrieving data from HuggingFace dataset repo"""
15
+
16
+ def __init__(self, repo_id: str = "Gamahea/lemm-dataset"):
17
+ """
18
+ Initialize HF storage service
19
+
20
+ Args:
21
+ repo_id: HuggingFace dataset repository ID
22
+ """
23
+ self.repo_id = repo_id
24
+ self.local_cache = Path("hf_cache")
25
+ self.local_cache.mkdir(exist_ok=True)
26
+
27
+ logger.info(f"HF Storage initialized for repo: {repo_id}")
28
+
29
+ # Try to import huggingface_hub
30
+ try:
31
+ from huggingface_hub import HfApi, hf_hub_download, upload_folder
32
+ self.api = HfApi()
33
+ self.has_hf = True
34
+ logger.info("✅ HuggingFace Hub available")
35
+ except ImportError:
36
+ logger.warning("⚠️ huggingface_hub not available, using local storage only")
37
+ self.has_hf = False
38
+
39
+ def download_all_loras(self, target_dir: Path) -> List[str]:
40
+ """
41
+ Download all LoRA adapters from HF repo
42
+
43
+ Args:
44
+ target_dir: Local directory to download to
45
+
46
+ Returns:
47
+ List of downloaded LoRA names
48
+ """
49
+ if not self.has_hf:
50
+ logger.warning("HuggingFace Hub not available")
51
+ return []
52
+
53
+ try:
54
+ from huggingface_hub import snapshot_download
55
+
56
+ logger.info(f"Downloading LoRAs from {self.repo_id}/loras...")
57
+
58
+ # Download loras folder
59
+ loras_path = snapshot_download(
60
+ repo_id=self.repo_id,
61
+ repo_type="dataset",
62
+ allow_patterns="loras/*",
63
+ local_dir=self.local_cache,
64
+ local_dir_use_symlinks=False
65
+ )
66
+
67
+ # Copy to target directory
68
+ source_loras = Path(loras_path) / "loras"
69
+ if source_loras.exists():
70
+ target_dir.mkdir(parents=True, exist_ok=True)
71
+
72
+ downloaded = []
73
+ for lora_dir in source_loras.iterdir():
74
+ if lora_dir.is_dir():
75
+ dest = target_dir / lora_dir.name
76
+ if dest.exists():
77
+ shutil.rmtree(dest)
78
+ shutil.copytree(lora_dir, dest)
79
+ downloaded.append(lora_dir.name)
80
+ logger.info(f"Downloaded LoRA: {lora_dir.name}")
81
+
82
+ return downloaded
83
+
84
+ return []
85
+
86
+ except Exception as e:
87
+ logger.error(f"Failed to download LoRAs: {e}")
88
+ return []
89
+
90
+ def download_all_datasets(self, target_dir: Path) -> List[str]:
91
+ """
92
+ Download all prepared datasets from HF repo
93
+
94
+ Args:
95
+ target_dir: Local directory to download to
96
+
97
+ Returns:
98
+ List of downloaded dataset keys
99
+ """
100
+ if not self.has_hf:
101
+ logger.warning("HuggingFace Hub not available")
102
+ return []
103
+
104
+ try:
105
+ from huggingface_hub import snapshot_download
106
+
107
+ logger.info(f"Downloading datasets from {self.repo_id}/datasets...")
108
+
109
+ # Download datasets folder
110
+ datasets_path = snapshot_download(
111
+ repo_id=self.repo_id,
112
+ repo_type="dataset",
113
+ allow_patterns="datasets/*",
114
+ local_dir=self.local_cache,
115
+ local_dir_use_symlinks=False
116
+ )
117
+
118
+ # Copy to target directory
119
+ source_datasets = Path(datasets_path) / "datasets"
120
+ if source_datasets.exists():
121
+ target_dir.mkdir(parents=True, exist_ok=True)
122
+
123
+ downloaded = []
124
+ for dataset_dir in source_datasets.iterdir():
125
+ if dataset_dir.is_dir():
126
+ dest = target_dir / dataset_dir.name
127
+ if dest.exists():
128
+ shutil.rmtree(dest)
129
+ shutil.copytree(dataset_dir, dest)
130
+ downloaded.append(dataset_dir.name)
131
+ logger.info(f"Downloaded dataset: {dataset_dir.name}")
132
+
133
+ return downloaded
134
+
135
+ return []
136
+
137
+ except Exception as e:
138
+ logger.error(f"Failed to download datasets: {e}")
139
+ return []
140
+
141
+ def upload_lora(self, lora_dir: Path) -> bool:
142
+ """
143
+ Upload a LoRA adapter to HF repo
144
+
145
+ Args:
146
+ lora_dir: Local LoRA directory
147
+
148
+ Returns:
149
+ True if successful
150
+ """
151
+ if not self.has_hf:
152
+ logger.warning("HuggingFace Hub not available")
153
+ return False
154
+
155
+ try:
156
+ from huggingface_hub import upload_folder
157
+
158
+ logger.info(f"Uploading LoRA {lora_dir.name} to {self.repo_id}...")
159
+
160
+ upload_folder(
161
+ repo_id=self.repo_id,
162
+ repo_type="dataset",
163
+ folder_path=str(lora_dir),
164
+ path_in_repo=f"loras/{lora_dir.name}",
165
+ commit_message=f"Add/Update LoRA: {lora_dir.name}"
166
+ )
167
+
168
+ logger.info(f"✅ Uploaded LoRA: {lora_dir.name}")
169
+ return True
170
+
171
+ except Exception as e:
172
+ logger.error(f"Failed to upload LoRA: {e}")
173
+ return False
174
+
175
+ def upload_dataset(self, dataset_dir: Path) -> bool:
176
+ """
177
+ Upload a prepared dataset to HF repo
178
+
179
+ Args:
180
+ dataset_dir: Local dataset directory
181
+
182
+ Returns:
183
+ True if successful
184
+ """
185
+ if not self.has_hf:
186
+ logger.warning("HuggingFace Hub not available")
187
+ return False
188
+
189
+ try:
190
+ from huggingface_hub import upload_folder
191
+
192
+ logger.info(f"Uploading dataset {dataset_dir.name} to {self.repo_id}...")
193
+
194
+ upload_folder(
195
+ repo_id=self.repo_id,
196
+ repo_type="dataset",
197
+ folder_path=str(dataset_dir),
198
+ path_in_repo=f"datasets/{dataset_dir.name}",
199
+ commit_message=f"Add/Update dataset: {dataset_dir.name}"
200
+ )
201
+
202
+ logger.info(f"✅ Uploaded dataset: {dataset_dir.name}")
203
+ return True
204
+
205
+ except Exception as e:
206
+ logger.error(f"Failed to upload dataset: {e}")
207
+ return False
208
+
209
+ def sync_on_startup(self, loras_dir: Path, datasets_dir: Path) -> Dict[str, List[str]]:
210
+ """
211
+ Sync data from HF repo on app startup
212
+
213
+ Args:
214
+ loras_dir: Local LoRA directory
215
+ datasets_dir: Local datasets directory
216
+
217
+ Returns:
218
+ Dict with 'loras' and 'datasets' lists
219
+ """
220
+ result = {'loras': [], 'datasets': []}
221
+
222
+ logger.info("🔄 Syncing from HuggingFace repo...")
223
+
224
+ # Download LoRAs
225
+ loras = self.download_all_loras(loras_dir)
226
+ result['loras'] = loras
227
+
228
+ # Download datasets
229
+ datasets = self.download_all_datasets(datasets_dir)
230
+ result['datasets'] = datasets
231
+
232
+ logger.info(f"✅ Sync complete: {len(loras)} LoRAs, {len(datasets)} datasets")
233
+
234
+ return result
backend/services/lyricmind_service.py CHANGED
@@ -109,10 +109,21 @@ class LyricMindService:
109
 
110
  # Try to generate with text model
111
  if self.model is not None and self.tokenizer is not None:
 
112
  lyrics = self._generate_with_model(prompt, effective_style, duration, analysis)
113
  else:
114
- # Fallback: placeholder lyrics
115
- lyrics = self._generate_placeholder(prompt, effective_style, duration)
 
 
 
 
 
 
 
 
 
 
116
 
117
  logger.info("Lyrics generated successfully")
118
  return lyrics
@@ -198,7 +209,9 @@ class LyricMindService:
198
  Returns:
199
  Placeholder lyrics
200
  """
201
- logger.warning("Using placeholder lyrics - LyricMind model not loaded")
 
 
202
 
203
  # Estimate number of lines based on duration
204
  lines_per_30s = 8
@@ -210,9 +223,9 @@ class LyricMindService:
210
  f"Style: {style}",
211
  "",
212
  "[Chorus]",
213
- "This is a placeholder",
214
- "Generated by LyricMind AI",
215
- "Replace with actual model output",
216
  ]
217
 
218
  # Pad to desired length
 
109
 
110
  # Try to generate with text model
111
  if self.model is not None and self.tokenizer is not None:
112
+ logger.info("Using AI model for lyrics generation")
113
  lyrics = self._generate_with_model(prompt, effective_style, duration, analysis)
114
  else:
115
+ logger.warning("AI model not available, attempting to load...")
116
+ # Try to initialize again before falling back
117
+ try:
118
+ self.is_initialized = False
119
+ self._initialize_model()
120
+ if self.model is not None:
121
+ lyrics = self._generate_with_model(prompt, effective_style, duration, analysis)
122
+ else:
123
+ lyrics = self._generate_placeholder(prompt, effective_style, duration)
124
+ except:
125
+ logger.error("Failed to load AI model, using placeholder")
126
+ lyrics = self._generate_placeholder(prompt, effective_style, duration)
127
 
128
  logger.info("Lyrics generated successfully")
129
  return lyrics
 
209
  Returns:
210
  Placeholder lyrics
211
  """
212
+ logger.warning("⚠️ PLACEHOLDER LYRICS - LyricMind AI model not loaded")
213
+ logger.warning(f"Model path checked: {self.model_path}")
214
+ logger.warning("Please ensure text_generator model is available in models/ directory")
215
 
216
  # Estimate number of lines based on duration
217
  lines_per_30s = 8
 
223
  f"Style: {style}",
224
  "",
225
  "[Chorus]",
226
+ "⚠️ PLACEHOLDER - Model not loaded",
227
+ "LyricMind AI requires text_generator model",
228
+ "Check logs for model loading errors",
229
  ]
230
 
231
  # Pad to desired length
backend/services/mastering_service.py CHANGED
@@ -436,19 +436,6 @@ class MasteringService:
436
  def __init__(self):
437
  """Initialize mastering service"""
438
  logger.info("Mastering service initialized")
439
- "Retro 80s",
440
- "80s digital warmth and punch",
441
- [
442
- HighpassFilter(cutoff_frequency_hz=35),
443
- LowShelfFilter(cutoff_frequency_hz=100, gain_db=1.5, q=0.7),
444
- PeakFilter(cutoff_frequency_hz=800, gain_db=1.0, q=1.0),
445
- PeakFilter(cutoff_frequency_hz=3000, gain_db=2.0, q=1.2),
446
- PeakFilter(cutoff_frequency_hz=8000, gain_db=1.5, q=1.0),
447
- HighShelfFilter(cutoff_frequency_hz=10000, gain_db=1.0, q=0.8),
448
- Compressor(threshold_db=-10, ratio=4.0, attack_ms=5, release_ms=100),
449
- Limiter(threshold_db=-0.5, release_ms=80)
450
- ]
451
- ),
452
 
453
  # Specialized Presets
454
  "vocal_focused": MasteringPreset(
 
436
  def __init__(self):
437
  """Initialize mastering service"""
438
  logger.info("Mastering service initialized")
 
 
 
 
 
 
 
 
 
 
 
 
 
439
 
440
  # Specialized Presets
441
  "vocal_focused": MasteringPreset(
requirements.txt CHANGED
@@ -1,36 +1,41 @@
1
- # Core dependencies for HuggingFace Spaces deployment
2
- gradio==4.44.0
3
- spaces>=0.28.3
4
- numpy>=1.24.0,<2.0.0
 
 
5
  scipy>=1.10.0
6
  librosa>=0.10.0
7
  soundfile>=0.12.0
8
  pydantic>=2.0.0
 
9
  pyyaml>=6.0
 
10
 
11
- # PyTorch - GPU mode for ZeroGPU
12
- torch>=2.4.0,<2.5.0
13
- torchaudio>=2.4.0,<2.5.0
 
 
14
 
15
- # DiffRhythm2 dependencies
16
- torchdiffeq>=0.2.4
17
  phonemizer>=3.2.0
18
- muq>=0.1.0
19
- jieba>=0.42.0
20
- pypinyin>=0.50.0
21
- cn2an>=0.5.0
22
- onnxruntime>=1.15.0
23
- pykakasi>=2.3.0
24
- unidecode>=1.3.0
25
- py3langid>=0.2.2
26
- pyopenjtalk>=0.3.0
27
- inflect>=7.0.0
28
 
29
  # AI Model dependencies
30
- transformers==4.47.1
31
  diffusers>=0.21.0
32
  sentencepiece>=0.1.99
33
- protobuf>=3.20.0,<5.0.0
34
  accelerate>=0.20.0
35
  einops>=0.7.0
36
  omegaconf>=2.3.0
@@ -40,15 +45,19 @@ pedalboard>=0.7.0
40
  pydub>=0.25.1
41
  resampy>=0.4.2
42
 
 
 
 
 
 
43
  # LoRA Training dependencies
44
  peft>=0.6.0 # Parameter-Efficient Fine-Tuning (LoRA adapters)
45
  datasets>=2.14.0 # HuggingFace datasets for training data management
46
  tensorboard>=2.13.0 # Training monitoring and visualization
 
47
 
48
  # Utilities
49
  tqdm>=4.65.0
50
  huggingface-hub>=0.17.0
51
  safetensors>=0.3.0
52
-
53
- # System dependencies note:
54
- # espeak-ng is required by phonemizer and should be installed via packages.txt
 
1
+ # Core dependencies
2
+ flask>=3.0.0
3
+ flask-cors>=4.0.0
4
+ gradio>=4.0.0
5
+ huggingface-hub>=0.20.0 # For HF dataset repo storage
6
+ numpy>=1.24.0
7
  scipy>=1.10.0
8
  librosa>=0.10.0
9
  soundfile>=0.12.0
10
  pydantic>=2.0.0
11
+ python-dotenv>=1.0.0
12
  pyyaml>=6.0
13
+ requests>=2.31.0
14
 
15
+ # PyTorch - CPU mode for compatibility
16
+ # Note: DiffRhythm2 requires torch>=2.4 which is incompatible with torch-directml
17
+ # Using CPU mode to avoid version conflicts. For GPU acceleration, use NVIDIA CUDA.
18
+ torch>=2.4.0
19
+ torchaudio>=2.4.0
20
 
21
+ # DiffRhythm 2 core dependencies
22
+ torchdiffeq>=0.2.4 # Required for CFM (flow matching)
23
  phonemizer>=3.2.0
24
+ muq>=0.1.0 # MuQ-MuLan style encoder for music generation
25
+ jieba>=0.42.0 # Chinese text segmentation
26
+ pypinyin>=0.50.0 # Chinese to pinyin conversion
27
+ cn2an>=0.5.0 # Chinese number to text
28
+ onnxruntime>=1.15.0 # For g2p Chinese model
29
+ pykakasi>=2.3.0 # Japanese text processing
30
+ pyopenjtalk; python_version < "3.12" # Japanese phonetics (Python 3.11 compatible)
31
+ unidecode>=1.3.0 # Text normalization
32
+ py3langid>=0.2.2 # Language detection
 
33
 
34
  # AI Model dependencies
35
+ transformers==4.47.1 # Pinned for DiffRhythm2 compatibility
36
  diffusers>=0.21.0
37
  sentencepiece>=0.1.99
38
+ protobuf>=3.20.0
39
  accelerate>=0.20.0
40
  einops>=0.7.0
41
  omegaconf>=2.3.0
 
45
  pydub>=0.25.1
46
  resampy>=0.4.2
47
 
48
+ # Audio quality enhancement
49
+ demucs==4.0.1 # Stem separation
50
+ noisereduce>=3.0.0 # Noise reduction
51
+ audiosr>=0.0.7 # Audio super resolution (upscaling to 48kHz)
52
+
53
  # LoRA Training dependencies
54
  peft>=0.6.0 # Parameter-Efficient Fine-Tuning (LoRA adapters)
55
  datasets>=2.14.0 # HuggingFace datasets for training data management
56
  tensorboard>=2.13.0 # Training monitoring and visualization
57
+ wandb>=0.15.0 # Optional: Advanced experiment tracking
58
 
59
  # Utilities
60
  tqdm>=4.65.0
61
  huggingface-hub>=0.17.0
62
  safetensors>=0.3.0
63
+ gitpython>=3.1.0