noblebarkrr commited on
Commit
1b94eeb
·
verified ·
1 Parent(s): 6fa5e37

Обновление плагина для создания ансамблей Ensembless.

Browse files

Обновление добавляет пресеты, и инвертирования ансамбля.

Обычное инвертирование (выкл, инвертирование через кнопку) :
- Вычитает результат ансамбля из оригинала с помощью спектрограммы, либо противофазы

Инвертирование ансамбля (вкл) :
- Берет стемы, противоположные выбранным (если вы выбрали правильные перевернутые стемы) и из них создает ансамбль с методом, противоположным выбранному (если включено переворачивание весов, будут использованы и перевернутые весы)

Совместимо с текущим репозиторием https://github.com/noblebarkrr/mvsepless/tree/beta в ветке beta

Files changed (1) hide show
  1. ensembless_v2.py +898 -0
ensembless_v2.py ADDED
@@ -0,0 +1,898 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import pandas as pd
4
+ import tempfile
5
+ import os
6
+ from separator.ensemble import ensemble_audio_files
7
+ from pydub.utils import mediainfo
8
+ from pydub import AudioSegment
9
+ import numpy as np
10
+ import librosa
11
+ import librosa.display
12
+ import soundfile as sf
13
+ from separator.audio_writer import write_audio_file
14
+ from multi_inference import MVSEPLESS
15
+ from pydub.exceptions import CouldntDecodeError
16
+
17
+ mvsepless = MVSEPLESS()
18
+
19
+ TRANSLATIONS = {
20
+ "ru": {
21
+ "app_title": "EnsembLess",
22
+ "auto_ensemble": "Авто-ансамбль",
23
+ "invert_ensemble": "Инвертировать ансамбль",
24
+ "give_name_preset": "Дайте имя пресету",
25
+ "export": "Экспорт",
26
+ "import": "Импорт",
27
+ "manual_ensemble": "Ручной ансамбль",
28
+ "inverter": "Инвертер",
29
+ "model_selection": "Выберите модель для добавления в ансамбль",
30
+ "model_type": "Тип модели",
31
+ "model_name": "Имя модели",
32
+ "stem_selection": "Стем, который будет использован в ансамбле",
33
+ "weight": "Весы",
34
+ "invert_weights": "Использовать перевернутые весы для инвертированного стема",
35
+ "add_button": "➕ Добавить",
36
+ "current_ensemble": "Текущий ансамбль",
37
+ "remove_index": "Индекс модели, который хотите удалить (начинается с 1)",
38
+ "remove_button": "❌ Удалить",
39
+ "clear_button": "Очистить",
40
+ "input_audio": "Входное аудио",
41
+ "settings": "Настройки",
42
+ "method": "Метод",
43
+ "output_format": "Формат вывода",
44
+ "run_button": "Создать ансамбль",
45
+ "results": "Результаты",
46
+ "inverted_result": "Инвертированный результат",
47
+ "invert_method": "Метод инвертирования",
48
+ "invert_button": "Инвертировать",
49
+ "audio_files": "Аудио файлы",
50
+ "weights_input": "Весы",
51
+ "main_audio": "Основное аудио",
52
+ "audio_to_remove": "Аудио для удаления",
53
+ "processing_method": "Метод обработки",
54
+ "analyze_title": "РЕЗУЛЬТАТЫ АНАЛИЗА:",
55
+ "all_same_rate": "✅ ВСЕ ФАЙЛЫ имеют одинаковую частоту дискретизации: {rate} Hz",
56
+ "different_rates": "⚠️ Файлы имеют РАЗНУЮ частоту дискретизации",
57
+ "resample_warning": "К загруженному аудио автоматически применён ресэмплинг для лучшего инвертирования",
58
+ "error_no_files": "Ошибка: файлы не загружены",
59
+ "error_unsupported_format": "не поддерживаемый формат",
60
+ "error_general": "ошибка ({error})",
61
+ "error_no_models": "Добавьте хотя бы одну модель для создания ансамбля",
62
+ "error_no_audio": "Сначала загрузите аудио",
63
+ "error_both_audio": "Пожалуйста, загрузите оба аудиофайла",
64
+ "language": "Язык",
65
+ "batch_processing": "Пакетная обработка",
66
+ "batch_info": "Позволяет загрузить сразу несколько файлов",
67
+ "separation_info": "Информация о разделении",
68
+ "vocal_separation": "Разделение вокалы",
69
+ "stereo_mode": "Стерео режим",
70
+ "stem": "Стем",
71
+ "p_stem": "Основной стем",
72
+ "s_stem": "Инвертированный стем",
73
+ "vocal_multi_separation": "Мульти-вокал",
74
+ "ensemble": "Ансамбль",
75
+ "transform": "Преобразование",
76
+ "algorithm": "Алгоритм: {model_fullname}",
77
+ "output_format_info": "Формат выходных данных: {output_format}",
78
+ "process1": "Начало обработки",
79
+ "process2": "Модель",
80
+ "process3": "Автоматическое выравнивание длин аудио",
81
+ "process4": "Создание ансамбля",
82
+ "result_source": "Промежуточные файлы",
83
+ "local_path": "Указать путь к аудио локально",
84
+ "resample": "Ресэмпл"
85
+ },
86
+ "en": {
87
+ "app_title": "EnsembLess",
88
+ "auto_ensemble": "Auto-Ensemble",
89
+ "invert_ensemble": "Invert ensemble",
90
+ "give_name_preset": "Give name of preset",
91
+ "export": "Export",
92
+ "import": "Import",
93
+ "manual_ensemble": "Manual Ensemble",
94
+ "inverter": "Inverter",
95
+ "model_selection": "Select a model to add to the ensemble",
96
+ "model_type": "Model Type",
97
+ "model_name": "Model Name",
98
+ "stem_selection": "Stem to use in the ensemble",
99
+ "weight": "Weights",
100
+ "invert_weights": "Use inverted weights for inverted stem",
101
+ "add_button": "➕ Add",
102
+ "current_ensemble": "Current Ensemble",
103
+ "remove_index": "Index of model to remove (starts from 1)",
104
+ "remove_button": "❌ Remove",
105
+ "clear_button": "Clear",
106
+ "input_audio": "Input Audio",
107
+ "settings": "Settings",
108
+ "method": "Method",
109
+ "output_format": "Output Format",
110
+ "run_button": "Create Ensemble",
111
+ "results": "Results",
112
+ "inverted_result": "Inverted Result",
113
+ "invert_method": "Inversion Method",
114
+ "invert_button": "Invert",
115
+ "audio_files": "Audio Files",
116
+ "weights_input": "Weights",
117
+ "main_audio": "Main Audio",
118
+ "audio_to_remove": "Audio to Remove",
119
+ "processing_method": "Processing Method",
120
+ "analyze_title": "ANALYSIS RESULTS:",
121
+ "all_same_rate": "✅ ALL FILES have the same sample rate: {rate} Hz",
122
+ "different_rates": "⚠️ Files have DIFFERENT sample rates",
123
+ "resample_warning": "Resampling applied automatically for better inversion",
124
+ "error_no_files": "Error: no files uploaded",
125
+ "error_unsupported_format": "unsupported format",
126
+ "error_general": "error ({error})",
127
+ "error_no_models": "Add at least one model to create an ensemble",
128
+ "error_no_audio": "Please upload audio first",
129
+ "error_both_audio": "Please upload both audio files",
130
+ "language": "Language",
131
+ "batch_processing": "Batch Processing",
132
+ "batch_info": "Allows uploading multiple files at once",
133
+ "separation_info": "Separation Info",
134
+ "vocal_separation": "Vocal Separation",
135
+ "stereo_mode": "Stereo Mode",
136
+ "stem": "Stem",
137
+ "p_stem": "Primary stem",
138
+ "s_stem": "Secondary stem",
139
+ "vocal_multi_separation": "Multi-Vocal",
140
+ "ensemble": "Ensemble",
141
+ "transform": "Transform",
142
+ "algorithm": "Algorithm: {model_fullname}",
143
+ "output_format_info": "Output format: {output_format}",
144
+ "process1": "Start process",
145
+ "process2": "Model",
146
+ "process3": "Auto post-padding audios",
147
+ "process4": "Build ensemble",
148
+ "result_source": "Intermediate files",
149
+ "local_path": "Specify path to audio locally",
150
+ "resample": "Resample"
151
+ }
152
+ }
153
+
154
+ INVERT_METHODS = {
155
+ "min_fft": "max_fft",
156
+ "max_fft": "min_fft",
157
+ "min_wave": "max_wave",
158
+ "max_wave": "min_wave",
159
+ "median_fft": "median_fft",
160
+ "median_wave": "median_wave",
161
+ "avg_fft": "avg_fft",
162
+ "avg_wave": "avg_wave"
163
+ }
164
+
165
+ # Глобальная переменная для текущего языка
166
+ CURRENT_LANG = "ru"
167
+
168
+ def set_language(lang):
169
+ global CURRENT_LANG
170
+ CURRENT_LANG = lang
171
+
172
+ def t(key, **kwargs):
173
+ """Функция для получения перевода с подстановкой значений"""
174
+ translation = TRANSLATIONS[CURRENT_LANG].get(key, key)
175
+ return translation.format(**kwargs) if kwargs else translation
176
+
177
+
178
+ # Фиксированные параметры для STFT
179
+ N_FFT = 2048
180
+ WIN_LENGTH = 2048
181
+ HOP_LENGTH = WIN_LENGTH // 4
182
+
183
+ class Inverter:
184
+ def __init__(self):
185
+ self.test = "test"
186
+
187
+ def load_audio(self, filepath):
188
+ """Загрузка аудиофайла с помощью librosa"""
189
+ if filepath is None:
190
+ return None, None
191
+ try:
192
+ return librosa.load(filepath, sr=None, mono=False)
193
+ except Exception as e:
194
+ print(f"Ошибка загрузки аудио: {e}")
195
+ return None, None
196
+
197
+ def process_channel(self, y1_ch, y2_ch, sr, method):
198
+ """Обработка одного аудиоканала"""
199
+ if method == "waveform":
200
+ return y1_ch - y2_ch
201
+
202
+ elif method == "spectrogram":
203
+ # Вычисляем спектрограммы
204
+ S1 = librosa.stft(y1_ch, n_fft=N_FFT, hop_length=HOP_LENGTH, win_length=WIN_LENGTH)
205
+ S2 = librosa.stft(y2_ch, n_fft=N_FFT, hop_length=HOP_LENGTH, win_length=WIN_LENGTH)
206
+
207
+ # Амплитудные спектрограммы
208
+ mag1 = np.abs(S1)
209
+ mag2 = np.abs(S2)
210
+
211
+ # Спектральное вычитание
212
+ mag_result = np.maximum(mag1 - mag2, 0)
213
+
214
+ # Сохраняем фазовую информацию исходного сигнала
215
+ phase = np.angle(S1)
216
+
217
+ # Комбинируем амплитуду результата с фазой
218
+ S_result = mag_result * np.exp(1j * phase)
219
+
220
+ # Обратное преобразование
221
+ return librosa.istft(
222
+ S_result,
223
+ n_fft=N_FFT,
224
+ hop_length=HOP_LENGTH,
225
+ win_length=WIN_LENGTH,
226
+ length=len(y1_ch)
227
+ )
228
+
229
+ def process_audio(self, audio1_path, audio2_path, out_format, method):
230
+ # Загрузка аудиофайлов
231
+ y1, sr1 = self.load_audio(audio1_path)
232
+ y2, sr2 = self.load_audio(audio2_path)
233
+
234
+ if sr1 is None or sr2 is None:
235
+ raise gr.Error(t("error_both_audio"))
236
+
237
+ # Определяем количество каналов
238
+ channels1 = 1 if y1.ndim == 1 else y1.shape[0]
239
+ channels2 = 1 if y2.ndim == 1 else y2.shape[0]
240
+
241
+ # Преобразование в форму (samples, channels)
242
+ if channels1 > 1:
243
+ y1 = y1.T # (channels, samples) -> (samples, channels)
244
+ else:
245
+ y1 = y1.reshape(-1, 1)
246
+
247
+ if channels2 > 1:
248
+ y2 = y2.T # (channels, samples) -> (samples, channels)
249
+ else:
250
+ y2 = y2.reshape(-1, 1)
251
+
252
+ # Ресемплинг до одинаковой частоты дискретизации
253
+ if sr1 != sr2:
254
+ if channels2 > 1:
255
+ # Ресемплинг для каждого канала отдельно
256
+ y2_resampled = np.zeros((len(y2), channels2), dtype=np.float32)
257
+ for c in range(channels2):
258
+ y2_resampled[:, c] = librosa.resample(
259
+ y2[:, c],
260
+ orig_sr=sr2,
261
+ target_sr=sr1
262
+ )
263
+ y2 = y2_resampled
264
+ else:
265
+ y2 = librosa.resample(y2[:, 0], orig_sr=sr2, target_sr=sr1)
266
+ y2 = y2.reshape(-1, 1)
267
+ sr2 = sr1
268
+
269
+ # Приводим к одинаковой длине
270
+ min_len = min(len(y1), len(y2))
271
+ y1 = y1[:min_len]
272
+ y2 = y2[:min_len]
273
+
274
+ # Обрабатываем каждый канал отдельно
275
+ result_channels = []
276
+
277
+ # Если основной сигнал моно, а удаляемый стерео - преобразуем удаляемый в моно
278
+ if channels1 == 1 and channels2 > 1:
279
+ y2 = y2.mean(axis=1, keepdims=True)
280
+ channels2 = 1
281
+
282
+ for c in range(channels1):
283
+ # Выбираем канал для основного сигнала
284
+ y1_ch = y1[:, c]
285
+
286
+ # Выбираем канал для удаляемого сигнала
287
+ if channels2 == 1:
288
+ y2_ch = y2[:, 0]
289
+ else:
290
+ # Если каналов удаляемого сигнала больше, используем соответствующий канал
291
+ y2_ch = y2[:, min(c, channels2-1)]
292
+
293
+ # Обрабатываем канал
294
+ result_ch = self.process_channel(y1_ch, y2_ch, sr1, method)
295
+ result_channels.append(result_ch)
296
+
297
+ # Собираем каналы в один массив
298
+ if len(result_channels) > 1:
299
+ result = np.column_stack(result_channels)
300
+ else:
301
+ result = np.array(result_channels[0])
302
+
303
+ # Нормализация (предотвращение клиппинга)
304
+ if result.ndim > 1:
305
+ # Для многоканального аудио нормализуем каждый канал отдельно
306
+ for c in range(result.shape[1]):
307
+ channel = result[:, c]
308
+ max_val = np.max(np.abs(channel))
309
+ if max_val > 0:
310
+ result[:, c] = channel * 0.9 / max_val
311
+ else:
312
+ max_val = np.max(np.abs(result))
313
+ if max_val > 0:
314
+ result = result * 0.9 / max_val
315
+
316
+ folder_path = os.path.dirname(audio2_path)
317
+
318
+ inverted_wav = os.path.join(folder_path, "inverted.wav")
319
+ sf.write(inverted_wav, result, sr1)
320
+ inverted = os.path.join(folder_path, f"inverted_ensemble.{out_format}")
321
+ write_audio_file(inverted, result.T, sr1, out_format, "320k")
322
+ return inverted, inverted_wav
323
+
324
+ class EnsembLess:
325
+ def __init__(self):
326
+ self.test = "test"
327
+
328
+ def get_model_types(self):
329
+ return mvsepless.get_mt()
330
+
331
+ def get_models_by_type(self, model_type):
332
+ return mvsepless.get_mn(model_type)
333
+
334
+ def get_stems_by_model(self, model_type, model_name):
335
+ stems = mvsepless.get_stems(model_type, model_name)
336
+ if set(stems) == {"bass", "drums", "vocals", "other"} or set(stems) == {"bass", "drums", "vocals", "other", "piano", "guitar"} and not mvsepless.get_tgt_inst(model_type, model_name):
337
+ stems.append("instrumental +")
338
+ stems.append("instrumental -")
339
+ return stems
340
+
341
+ def get_invert_stems_by_model(self, model_type, model_name, primary_stem):
342
+ invert_stems = []
343
+ stems = mvsepless.get_stems(model_type, model_name)
344
+ for stem in stems:
345
+ if stem != primary_stem:
346
+ invert_stems.append(stem)
347
+
348
+ if not mvsepless.get_tgt_inst(model_type, model_name) and model_type not in ["vr", "mdx"]:
349
+
350
+ invert_stems.append("inverted +")
351
+ invert_stems.append("inverted -")
352
+
353
+ return invert_stems
354
+
355
+ def invert_weights(self, weights):
356
+ total_weight = sum(weights)
357
+ return [total_weight - w for w in weights]
358
+
359
+ def analyze_sample_rate(self, files):
360
+ """
361
+ Анализирует частоту дискретизации для списка аудиофайлов
362
+ Возвращает форматированную строку с результатами
363
+ """
364
+ if not files:
365
+ return t("error_no_files")
366
+
367
+ results = []
368
+ common_rate = None
369
+ all_same = True
370
+
371
+ for file_info in files:
372
+ try:
373
+ # Создаем аудиосегмент из файла
374
+ audio = AudioSegment.from_file(file_info.name)
375
+ rate = audio.frame_rate
376
+
377
+ # Проверяем единообразие частоты
378
+ if common_rate is None:
379
+ common_rate = rate
380
+ elif common_rate != rate:
381
+ all_same = False
382
+
383
+ results.append(f"{file_info.name.split('/')[-1]}: {rate} Hz")
384
+
385
+ except CouldntDecodeError:
386
+ results.append(f"{file_info.name.split('/')[-1]}: {t('error_unsupported_format')}")
387
+ except Exception as e:
388
+ results.append(f"{file_info.name.split('/')[-1]}: {t('error_general', error=str(e))}")
389
+
390
+ # Форматируем итоговый результат
391
+ header = t("analyze_title") + "\n" + "-" * 50 + "\n"
392
+ body = "\n".join(results)
393
+ footer = "\n" + "-" * 50 + "\n"
394
+
395
+ if all_same and common_rate is not None:
396
+ footer += f"\n{t('all_same_rate', rate=common_rate)}"
397
+ elif common_rate is not None:
398
+ footer += f"\n{t('different_rates')}"
399
+
400
+ return header + body + footer
401
+
402
+ def resample_audio(self, audio_path):
403
+ if not audio_path or not os.path.isfile(audio_path):
404
+ gr.Warning(t("error_no_audio"))
405
+ return None
406
+
407
+ original_name = os.path.splitext(os.path.basename(audio_path))[0]
408
+ folder_path = os.path.dirname(audio_path)
409
+ resampled_path = os.path.join(folder_path, f"resampled_{original_name}.wav")
410
+
411
+ target_sr = 44100
412
+
413
+ # Загрузка аудио через librosa с сохранением оригинальной структуры каналов
414
+ y, orig_sr = librosa.load(audio_path, sr=None, mono=False)
415
+
416
+ # Определение типа аудио (моно/стерео)
417
+ if y.ndim == 1:
418
+ channels = 1
419
+ y = y.reshape(-1, 1)
420
+ else:
421
+ channels = y.shape[0]
422
+ y = y.T
423
+
424
+ # Ресемплинг только если необходима смена частоты
425
+ if orig_sr != target_sr:
426
+ resampled_channels = []
427
+ for channel in range(channels):
428
+ channel_data = y[:, channel]
429
+ resampled = librosa.resample(
430
+ y=channel_data,
431
+ orig_sr=orig_sr,
432
+ target_sr=target_sr,
433
+ res_type="kaiser_best" # Высококачественный метод
434
+ )
435
+ resampled_channels.append(resampled)
436
+
437
+ # Синхронизация длины каналов
438
+ min_length = min(len(c) for c in resampled_channels)
439
+ resampled_data = np.vstack([c[:min_length] for c in resampled_channels]).T
440
+ else:
441
+ resampled_data = y
442
+
443
+ # Сохранение результата в формате WAV (16-bit PCM)
444
+ sf.write(
445
+ resampled_path,
446
+ resampled_data,
447
+ target_sr,
448
+ subtype="PCM_16"
449
+ )
450
+
451
+ gr.Warning(message=t("resample_warning"))
452
+ return resampled_path
453
+
454
+ def maximize_length_audio(self, output):
455
+ padded_files = []
456
+ audio_data = []
457
+ max_length = 0
458
+ for file in output:
459
+ data, sr = librosa.load(file, sr=None, mono=False)
460
+ if data.ndim == 1:
461
+ data = np.stack([data, data])
462
+ elif data.shape[0] != 2:
463
+ data = data.T
464
+ audio_data.append([file, data])
465
+ max_length = max(max_length, data.shape[1])
466
+
467
+ for i, [file, data] in enumerate(audio_data):
468
+ if data.shape[1] < max_length:
469
+ pad_width = ((0, 0), (0, max_length - data.shape[1]))
470
+ padded_data = np.pad(data, pad_width, mode='constant')
471
+ else:
472
+ padded_data = data
473
+ sf.write(file, padded_data.T, sr)
474
+ padded_files.append(file)
475
+ return padded_files
476
+
477
+ def maximize_length_audio_wav(self, output):
478
+ padded_files = []
479
+ audio_data = []
480
+ max_length = 0
481
+ for file in output:
482
+ data, sr = sf.read(file)
483
+ if data.ndim == 1:
484
+ data = np.stack([data, data])
485
+ elif data.shape[0] != 2:
486
+ data = data.T
487
+ audio_data.append([file, data])
488
+ max_length = max(max_length, data.shape[1])
489
+
490
+ for i, [file, data] in enumerate(audio_data):
491
+ if data.shape[1] < max_length:
492
+ pad_width = ((0, 0), (0, max_length - data.shape[1]))
493
+ padded_data = np.pad(data, pad_width, mode='constant')
494
+ else:
495
+ padded_data = data
496
+ sf.write(file, padded_data.T, sr)
497
+ padded_files.append(file)
498
+ return padded_files
499
+
500
+ def manual_ensemble(self, input_audios, method, weights, out_format):
501
+ temp_dir = tempfile.mkdtemp()
502
+ weights = [float(x) for x in weights.split(",")]
503
+ # padded_files = self.maximize_length_audio(input_audios)
504
+ a1, a2 = ensemble_audio_files(input_audios, output=os.path.join(temp_dir, f"ensemble_{method}"), ensemble_type=method, weights=weights, out_format=out_format)
505
+ return a1, a2
506
+
507
+ def auto_ensemble(self, input_audio, input_settings, type, out_format, invert_weights, invert_ensemble):
508
+
509
+ progress = gr.Progress()
510
+ progress(0, desc=f"{t('process1')}...")
511
+
512
+ base_name = os.path.splitext(os.path.basename(input_audio))[0]
513
+ temp_dir = tempfile.mkdtemp()
514
+ source_files = []
515
+ output_p_files = []
516
+ output_s_files = []
517
+ output_p_weights = []
518
+
519
+ block_count = len(input_settings)
520
+
521
+ for i, (input_model, weight, p_stem, s_stem) in enumerate(input_settings):
522
+ output_s_files.append(None)
523
+ progress(i / block_count, desc=f"{t('process2')} {i+1}/{block_count}")
524
+ model_type, model_name = input_model.split(" / ")
525
+ output_dir_p = os.path.join(temp_dir, f"{model_type}_{model_name}_p_stems")
526
+ output_p = mvsepless.separator(input_file=input_audio, output_dir=output_dir_p, model_type=model_type, model_name=model_name, ext_inst=True, vr_aggr=10, output_format="wav", template="MODEL_STEM", call_method="cli")
527
+ for stem, file in output_p:
528
+ source_files.append(file)
529
+ if stem == p_stem:
530
+ output_p_files.append(file)
531
+ output_p_weights.append(weight)
532
+ elif invert_ensemble:
533
+ if stem == s_stem:
534
+ output_s_files[i] = file
535
+
536
+ if invert_ensemble:
537
+ if not output_s_files[i]:
538
+
539
+ output_dir_s = os.path.join(temp_dir, f"{model_type}_{model_name}_s_stems")
540
+ output_s = mvsepless.separator(input_file=input_audio, output_dir=output_dir_s, model_type=model_type, model_name=model_name, ext_inst=True, vr_aggr=10, output_format="wav", template="MODEL_STEM", call_method="cli", selected_stems=[p_stem if not mvsepless.get_tgt_inst(model_type, model_name) else "both"])
541
+ for stem, file in output_s:
542
+ source_files.append(file)
543
+ if stem == s_stem:
544
+ output_s_files[i] = file
545
+ source_files.append(file)
546
+
547
+ progress(0.9, desc=f"{t('process3')}...")
548
+ # output_p_files = self.maximize_length_audio_wav(output_p_files)
549
+ if invert_ensemble:
550
+ # output_s_files = self.maximize_length_audio_wav(output_s_files)
551
+ pass
552
+ progress(0.95, desc=f"{t('process4')}...")
553
+ if invert_ensemble:
554
+ if invert_weights:
555
+ output_s_weights = self.invert_weights(output_p_weights)
556
+ else:
557
+ output_s_weights = output_p_weights
558
+ output_s, output_wav_s = ensemble_audio_files(files=output_s_files, output=os.path.join(temp_dir, f"ensemble_invert_{base_name}_{type}"), ensemble_type=INVERT_METHODS[type], weights=output_s_weights, out_format=out_format)
559
+ else:
560
+ output_s, output_wav_s = None, None
561
+
562
+ output_p, output_wav_p = ensemble_audio_files(files=output_p_files, output=os.path.join(temp_dir, f"ensemble_{base_name}_{type}"), ensemble_type=type, weights=output_p_weights, out_format=out_format)
563
+
564
+ return output_p, output_wav_p, output_s, output_wav_s, source_files
565
+
566
+ class EnsembleManager:
567
+ def __init__(self):
568
+ self.models = []
569
+ self.presets_dir = os.path.join(os.getcwd(), "presets")
570
+ os.makedirs(self.presets_dir, exist_ok=True)
571
+
572
+ def export_preset(self, name):
573
+ if not name:
574
+ name = "ensembless_preset"
575
+ filepath = os.path.join(self.presets_dir, f"{name}.json")
576
+ with open(filepath, 'w') as f:
577
+ json.dump(self.models, f)
578
+ return filepath
579
+
580
+ def import_preset(self, filepath):
581
+ with open(filepath, 'r') as f:
582
+ self.models = json.load(f)
583
+ return self.get_df()
584
+
585
+ def add_model(self, model_type, model_name, p_stem, s_stem, weight):
586
+ model_info = {
587
+ 'type': model_type,
588
+ 'name': model_name,
589
+ 'p_stem': p_stem,
590
+ 's_stem': s_stem,
591
+ 'weight': float(weight)
592
+ }
593
+ self.models.append(model_info)
594
+ return self.get_df()
595
+
596
+ def remove_model(self, index):
597
+ if 0 <= index < len(self.models):
598
+ del self.models[index]
599
+ return self.get_df()
600
+
601
+ def clear_models(self):
602
+ self.models = []
603
+ return self.get_df()
604
+
605
+ def get_df(self):
606
+ if not self.models:
607
+ columns = ["#", t("model_type"), t("model_name"), t("p_stem"), t("s_stem"), t("weight")]
608
+ return pd.DataFrame(columns=columns)
609
+
610
+ data = []
611
+ for i, model in enumerate(self.models):
612
+ data.append([
613
+ f"{i+1}",
614
+ model['type'],
615
+ model['name'],
616
+ model['p_stem'],
617
+ model['s_stem'],
618
+ model['weight']
619
+ ])
620
+ columns = ["#", t("model_type"), t("model_name"), t("p_stem"), t("s_stem"), t("weight")]
621
+ return pd.DataFrame(data, columns=columns)
622
+
623
+ def get_settings(self):
624
+ return [(f"{m['type']} / {m['name']}", m['weight'], m['p_stem'], m['s_stem']) for m in self.models]
625
+
626
+ inverter = Inverter()
627
+ manager = EnsembleManager()
628
+ ensembless = EnsembLess()
629
+
630
+ class EnsembLess_ui_updates:
631
+
632
+ def update_model_dropdown(self, model_type):
633
+ models = ensembless.get_models_by_type(model_type)
634
+ return gr.Dropdown(choices=models, value=models[0] if models else None)
635
+
636
+ def update_stem_dropdown(self, model_type, model_name):
637
+ stems = ensembless.get_stems_by_model(model_type, model_name)
638
+ return gr.Dropdown(choices=stems, value=stems[0] if stems else None)
639
+
640
+ def update_invert_stem_dropdown(self, model_type, model_name, primary_stem):
641
+ stems = ensembless.get_invert_stems_by_model(model_type, model_name, primary_stem)
642
+ return gr.Dropdown(choices=stems, value=stems[0] if stems else None)
643
+
644
+ def add_model(self, model_type, model_name, p_stem, s_stem, weight):
645
+ return manager.add_model(model_type, model_name, p_stem, s_stem, weight)
646
+
647
+ def remove_model(self, index):
648
+ if index >= 0:
649
+ return manager.remove_model(index-1) # Пользователь вводит начиная с 1, а индекс с 0
650
+ return manager.get_df()
651
+
652
+ def clear_all_models(self):
653
+ return manager.clear_models()
654
+
655
+ def run_ensemble(self, input_audio, ensemble_type, output_format, invert_weights, invert_ensemble):
656
+ if not manager.models:
657
+ raise gr.Error(t("error_no_models"))
658
+
659
+ if not input_audio:
660
+ raise gr.Error(t("error_no_audio"))
661
+
662
+ input_settings = manager.get_settings()
663
+
664
+ o, o_wav, i, i_wav, result_source = ensembless.auto_ensemble(
665
+ input_audio=input_audio,
666
+ input_settings=input_settings,
667
+ type=ensemble_type,
668
+ out_format=output_format,
669
+ invert_weights=invert_weights,
670
+ invert_ensemble=invert_ensemble,
671
+ )
672
+ return o, o_wav, i, i_wav, result_source
673
+
674
+ ensembless_ui = EnsembLess_ui_updates()
675
+
676
+ def ensembless_plugin_name():
677
+ return "EnsembLess"
678
+
679
+ # Создаем интерфейс
680
+ def ensembless_plugin(lang):
681
+ set_language(lang)
682
+
683
+ with gr.Tabs():
684
+ with gr.Tab(t("auto_ensemble")):
685
+ with gr.Row():
686
+ with gr.Column(scale=1):
687
+ # Секция добавления моделей
688
+ gr.Markdown(f"### {t('model_selection')}")
689
+ model_type = gr.Dropdown(
690
+ choices=ensembless.get_model_types(),
691
+ label=t("model_type"),
692
+ value=ensembless.get_model_types()[0] if ensembless.get_model_types() else None,
693
+ filterable=False
694
+ )
695
+ model_name = gr.Dropdown(
696
+ choices=ensembless.get_models_by_type(ensembless.get_model_types()[0]),
697
+ label=t("model_name"),
698
+ interactive=True,
699
+ value=ensembless.get_models_by_type(ensembless.get_model_types()[0])[0],
700
+ filterable=False
701
+ )
702
+ stem = gr.Dropdown(
703
+ choices=ensembless.get_stems_by_model(ensembless.get_model_types()[0], ensembless.get_models_by_type(ensembless.get_model_types()[0])[0]),
704
+ label=t("p_stem"),
705
+ interactive=True,
706
+ filterable=False
707
+ )
708
+ invert_stem = gr.Dropdown(
709
+ choices=ensembless.get_invert_stems_by_model(ensembless.get_model_types()[0], ensembless.get_models_by_type(ensembless.get_model_types()[0])[0], "vocals"),
710
+ label=t("s_stem"),
711
+ interactive=True,
712
+ filterable=False
713
+ )
714
+
715
+ weight = gr.Slider(
716
+ label=t("weight"),
717
+ value=1.0,
718
+ minimum=0.1,
719
+ maximum=10.0,
720
+ step=0.1
721
+ )
722
+ add_btn = gr.Button(t("add_button"), variant="primary")
723
+
724
+ with gr.Column(scale=2):
725
+ # Секция управления ансамблем
726
+ gr.Markdown(f"### {t('current_ensemble')}")
727
+ ensemble_df = gr.Dataframe(
728
+ value=manager.get_df(),
729
+ headers=["#", t("model_type"), t("model_name"), t("p_stem"), t("s_stem"), t("weight")],
730
+ datatype=["str", "str", "str", "str", "str", "number"],
731
+ interactive=False
732
+ )
733
+ with gr.Row(equal_height=True):
734
+ export_preset_name = gr.Textbox(label=t("give_name_preset"), interactive=True, value="ensembless_preset")
735
+ with gr.Column():
736
+ export_btn = gr.DownloadButton(t("export"), variant="secondary")
737
+ import_btn = gr.UploadButton(t("import"), file_types=[".json"], file_count="single")
738
+ with gr.Row(equal_height=True):
739
+ remove_idx = gr.Number(
740
+ label=t("remove_index"),
741
+ precision=0,
742
+ minimum=1,
743
+ interactive=True
744
+ )
745
+ with gr.Column():
746
+ remove_btn = gr.Button(t("remove_button"), variant="stop")
747
+ clear_btn = gr.Button(t("clear_button"), variant="stop")
748
+
749
+ # Секция запуска обработки
750
+ with gr.Row():
751
+ with gr.Column():
752
+ gr.Markdown(f"### {t('input_audio')}")
753
+ input_audio = gr.Audio(type="filepath", show_label=False)
754
+ input_audio_resampled = gr.Text(visible=False)
755
+
756
+ gr.Markdown(f"### {t('settings')}")
757
+ ensemble_type = gr.Dropdown(
758
+ choices=['avg_wave', 'median_wave', 'min_wave', 'max_wave',
759
+ 'avg_fft', 'median_fft', 'min_fft', 'max_fft'],
760
+ value='avg_fft',
761
+ label=t("method"),
762
+ filterable=False
763
+ )
764
+ invert_ensem = gr.Checkbox(label=t("invert_ensemble"))
765
+ invert_weights = gr.Checkbox(label=t("invert_weights"))
766
+ output_format = gr.Dropdown(
767
+ choices=["wav", "mp3", "flac", "m4a", "aac", "ogg", "opus", "aiff"],
768
+ value="mp3",
769
+ label=t("output_format"),
770
+ filterable=False
771
+ )
772
+ run_btn = gr.Button(t("run_button"), variant="primary")
773
+
774
+ with gr.Column():
775
+ with gr.Tab(t('results')):
776
+
777
+ with gr.Column():
778
+ output_audio = gr.Audio(label=t("results"), type="filepath", interactive=False, show_download_button=True)
779
+ output_wav = gr.Text(label="Результат в WAV", interactive=False, visible=False)
780
+
781
+ gr.Markdown(f"###### {t('inverted_result')}")
782
+
783
+ invert_method = gr.Radio(
784
+ choices=["waveform", "spectrogram"],
785
+ label=t("invert_method"),
786
+ value="waveform"
787
+ )
788
+ invert_btn = gr.Button(t("invert_button"))
789
+ inverted_output_audio = gr.Audio(label=t("inverted_result"), type="filepath", interactive=False, show_download_button=True)
790
+ inverted_wav = gr.Text(label="И��вертированный результат в WAV", interactive=False, visible=False)
791
+
792
+ with gr.Tab(t('result_source')):
793
+ result_source = gr.Files(interactive=False, label=t('result_source'))
794
+
795
+ stem.change(ensembless_ui.update_invert_stem_dropdown, inputs=[model_type, model_name, stem], outputs=invert_stem)
796
+
797
+ model_type.change(
798
+ ensembless_ui.update_model_dropdown,
799
+ inputs=model_type,
800
+ outputs=model_name
801
+ )
802
+ model_name.change(
803
+ ensembless_ui.update_stem_dropdown,
804
+ inputs=[model_type, model_name],
805
+ outputs=stem
806
+ )
807
+
808
+ ensemble_df.change(
809
+ manager.export_preset,
810
+ inputs=export_preset_name,
811
+ outputs=export_btn
812
+ )
813
+
814
+ export_preset_name.change(
815
+ manager.export_preset,
816
+ inputs=export_preset_name,
817
+ outputs=export_btn
818
+ )
819
+
820
+ import_btn.upload(
821
+ manager.import_preset,
822
+ inputs=import_btn,
823
+ outputs=ensemble_df
824
+ )
825
+
826
+ invert_btn.click(
827
+ inverter.process_audio,
828
+ inputs=[input_audio_resampled, output_wav, output_format, invert_method],
829
+ outputs=[inverted_output_audio, inverted_wav]
830
+ )
831
+
832
+ input_audio.upload(
833
+ ensembless.resample_audio,
834
+ inputs=input_audio,
835
+ outputs=input_audio_resampled
836
+ )
837
+
838
+ add_btn.click(
839
+ ensembless_ui.add_model,
840
+ inputs=[model_type, model_name, stem, invert_stem, weight],
841
+ outputs=ensemble_df
842
+ )
843
+
844
+ remove_btn.click(
845
+ ensembless_ui.remove_model,
846
+ inputs=remove_idx,
847
+ outputs=ensemble_df
848
+ )
849
+
850
+ clear_btn.click(
851
+ ensembless_ui.clear_all_models,
852
+ outputs=ensemble_df
853
+ )
854
+
855
+ run_btn.click(
856
+ ensembless_ui.run_ensemble,
857
+ inputs=[input_audio_resampled, ensemble_type, output_format, invert_weights, invert_ensem],
858
+ outputs=[output_audio, output_wav, inverted_output_audio, inverted_wav, result_source]
859
+ )
860
+
861
+ with gr.Tab(t("manual_ensemble")):
862
+ with gr.Row(equal_height=True):
863
+ input_files = gr.Files(show_label=False, type="filepath", file_types=[".wav", ".mp3", ".flac", ".m4a", ".aac", ".ogg", ".opus", ".aiff"])
864
+ with gr.Column():
865
+ info_audios = gr.Textbox(label="", interactive=False)
866
+ man_method = gr.Dropdown(
867
+ choices=['avg_wave', 'median_wave', 'min_wave', 'max_wave',
868
+ 'avg_fft', 'median_fft', 'min_fft', 'max_fft'],
869
+ value='avg_fft',
870
+ label=t("method"),
871
+ filterable=False
872
+ )
873
+
874
+ weights_input = gr.Textbox(label=t("weights_input"), value="1.0,1.0")
875
+
876
+ output_man_format = gr.Dropdown(
877
+ choices=["wav", "mp3", "flac", "m4a", "aac", "ogg", "opus", "aiff"],
878
+ value="mp3",
879
+ label=t("output_format"),
880
+ filterable=False
881
+ )
882
+
883
+ run_man_btn = gr.Button(t("run_button"), variant="primary")
884
+
885
+ output_man_audio = gr.Audio(label=t("results"), type="filepath", interactive=False, show_download_button=True)
886
+ output_man_wav = gr.Text(label="Результат в WAV", interactive=False, visible=False)
887
+
888
+ input_files.upload(
889
+ fn=ensembless.analyze_sample_rate,
890
+ inputs=input_files,
891
+ outputs=info_audios
892
+ )
893
+
894
+ run_man_btn.click(
895
+ ensembless.manual_ensemble,
896
+ inputs=[input_files, man_method, weights_input, output_man_format],
897
+ outputs=[output_man_audio, output_man_wav]
898
+ )