import gradio as gr import os, sys, subprocess import pandas as pd from datetime import datetime import tempfile import json if not __package__: from __init__ import Separator from downloader import dw_yt_dlp else: from .. import Separator from ..downloader import dw_yt_dlp class Plugin(Separator): def __init__(self): self.name = "Авто-цепочка разделений" self.requirements = [] self.install_requirements(self.requirements) def install_requirements(self, requirements: list): if requirements: cmd = [os.sys.executable, "-m", "pip", "install"] for pkg in requirements: cmd.append(pkg) result = subprocess.run(cmd, text=True, capture_output=True) class ModelManager(Separator): def __init__(self): self.data = [] self.dir_presets = os.path.join(tempfile.tempdir, "presets") os.makedirs(self.dir_presets, exist_ok=True) def save(self, name): if not name: name = "chainless_preset" filepath = os.path.join( self.dir_presets, f"{self.namer.short(self.namer.sanitize(name), length=50)}.json", ) with open(filepath, "w") as f: json.dump(self.data, f, indent=4, ensure_ascii=False) return filepath def load(self, filepath): with open(filepath, "r") as f: self.data = json.load(f) def add(self, mt, mn, s_stem, out_stem, int_stem): if int_stem: self.data.append((mt, mn, s_stem, out_stem, int_stem)) def replace(self, mt, mn, s_stem, out_stem, int_stem, index=1): if self.data: len_data = len(self.data) if index >= 1: if index <= len_data: self.data[index - 1] = (mt, mn, s_stem, out_stem, int_stem) elif index == 0: self.data[0] = (mt, mn, s_stem, out_stem, int_stem) def remove(self, index=1): if self.data: len_data = len(self.data) if index >= 1: if index <= len_data: del self.data[index - 1] elif index == 0: del self.data[0] def clear(self): self.data = [] def get_df(self): if not self.data: columns = [ "#", "Имя модели", "Выбранные стемы", "Остаток", "Промежуточный стем", ] return pd.DataFrame(columns=columns) data = [] for i, model in enumerate(self.data): data.append( [ f"{i+1}", model[1], str(model[2]), str(model[3]), model[4], ] ) columns = [ "#", "Имя модели", "Выбранные стемы", "Остаток", "Промежуточный стем", ] return pd.DataFrame(data, columns=columns) def UI(self): def get_output_stems(mt, mn, s_stem): output_stems = [] stems = self.model_manager.get_stems(mt, mn) if not s_stem: for stem in stems: output_stems.append(stem) if set(stems) == {"bass", "drums", "vocals", "other"} or set(stems) == { "bass", "drums", "vocals", "other", "guitar", "piano", }: output_stems.append("instrumental +") output_stems.append("instrumental -") elif s_stem: if len(stems) > 2: for stem in stems: if stem in s_stem: output_stems.append(stem) output_stems.append("inverted -") if len(stems) - len(s_stem) > 1: output_stems.append("inverted +") return output_stems def get_invert_output_stems(mt, mn, s_stem, out_stem): output_stems = [] stems = get_output_stems(mt, mn, s_stem) for stem in stems: if stem not in out_stem: output_stems.append(stem) return output_stems default_model = { "mt": self.model_manager.get_mt(), "mn": self.model_manager.get_mn(self.model_manager.get_mt()[0]), "stems": self.model_manager.get_stems( self.model_manager.get_mt()[0], self.model_manager.get_mn(self.model_manager.get_mt()[0])[0], ), "output_stems": get_output_stems( self.model_manager.get_mt()[0], self.model_manager.get_mn(self.model_manager.get_mt()[0])[0], [], ), "int_stems": get_invert_output_stems( self.model_manager.get_mt()[0], self.model_manager.get_mn(self.model_manager.get_mt()[0])[0], [], "vocals", ), } chain_manager = self.ModelManager() gr.Markdown("

Пресет

") with gr.Group(): with gr.Row(equal_height=True): export_preset_name = gr.Textbox( label="Имя пресета", interactive=True, value="chainless_preset", scale=9, ) export_btn = gr.DownloadButton( "Экспорт", variant="secondary", scale=3, interactive=True ) import_btn = gr.UploadButton( "Импорт", file_types=[".json"], file_count="single", scale=3, interactive=True, ) gr.Markdown("

Цепочка разделений

") with gr.Row(): with gr.Column(scale=3): model_type = gr.Dropdown( label="Тип модели", choices=default_model["mt"], value=default_model["mt"][0], interactive=True, filterable=False, ) model_name = gr.Dropdown( label="Имя модели", choices=default_model["mn"], value=default_model["mn"][0], interactive=True, filterable=False, ) selected_stems = gr.Dropdown( label="Выбранные стемы", choices=default_model["stems"], value=[], multiselect=True, interactive=False, filterable=False, ) output_stems = gr.Dropdown( label="Остаток", choices=default_model["output_stems"], value=[default_model["output_stems"][0]], multiselect=True, interactive=True, filterable=False, ) intermediate_stem = gr.Dropdown( label="Промежуточный стем", choices=default_model["int_stems"], value=default_model["int_stems"][0], interactive=True, filterable=False, ) @model_type.change(inputs=[model_type], outputs=[model_name]) def update_model_names(mt): model_names = self.model_manager.get_mn(mt) new_mn = model_names[0] if model_names else "" return gr.update(choices=model_names, value=new_mn) @model_name.change( inputs=[model_type, model_name], outputs=[selected_stems, output_stems, intermediate_stem], ) def update_stems_after_model_change(mt, mn): stems = self.model_manager.get_stems(mt, mn) _output_stems = get_output_stems(mt, mn, []) invert_stems = get_invert_output_stems( mt, mn, [], [_output_stems[0]] ) new_out_stem = [_output_stems[0]] new_inv_out_stem = invert_stems[0] return ( gr.update( choices=stems, value=[], interactive=False if len(stems) <= 2 else True, ), gr.update( choices=_output_stems, value=new_out_stem, max_choices=len(_output_stems) - 1, ), gr.update(choices=invert_stems, value=new_inv_out_stem), ) @selected_stems.change( inputs=[model_type, model_name, selected_stems], outputs=[output_stems, intermediate_stem], ) def update_invert_stems(mt, mn, s_stem): stems = get_output_stems(mt, mn, s_stem) new_i_stem = [stems[0]] invert_stems = get_invert_output_stems(mt, mn, s_stem, new_i_stem) return gr.update(choices=stems, value=new_i_stem), gr.update( choices=invert_stems, value=invert_stems[0] ) @output_stems.change( inputs=[model_type, model_name, selected_stems, output_stems], outputs=[intermediate_stem], ) def update_invert2_stems(mt, mn, s_stem, out_stem): invert_stems = get_invert_output_stems(mt, mn, s_stem, out_stem) return gr.update(choices=invert_stems, value=invert_stems[0]) model_add_button = gr.Button("Добавить", interactive=True) with gr.Column(scale=10): df = gr.DataFrame( value=chain_manager.get_df(), headers=[ "#", "Имя модели", "Выбранные стемы", "Остаток", "Промежуточный стем", ], datatype=["number", "str", "str", "str", "str"], interactive=False, ) with gr.Group(): with gr.Row(equal_height=True): with gr.Column(): model_index = gr.Number( label="Индекс модели", value=1, interactive=True ) model_clear_btn = gr.Button( "Очистить", variant="stop", interactive=True ) with gr.Column(): model_replace_btn = gr.Button( "Заменить", variant="primary", interactive=True ) model_delete_btn = gr.Button( "Удалить", variant="stop", interactive=True ) @model_add_button.click( inputs=[ model_type, model_name, selected_stems, output_stems, intermediate_stem, ], outputs=df, ) def add_model_to_auto_ensemble(mt, mn, s_stem, out_stem, int_stem): chain_manager.add(mt, mn, s_stem, out_stem, int_stem) return chain_manager.get_df() @model_replace_btn.click( inputs=[ model_type, model_name, selected_stems, output_stems, intermediate_stem, model_index, ], outputs=df, ) def replace_model_to_auto_ensemble( mt, mn, s_stem, out_stem, int_stem, index ): chain_manager.replace(mt, mn, s_stem, out_stem, int_stem, index) return chain_manager.get_df() @model_delete_btn.click(inputs=[model_index], outputs=df) def delete_model_to_auto_ensemble(index): chain_manager.remove(index) return chain_manager.get_df() @model_clear_btn.click(outputs=df) def clear_model_to_auto_ensemble(): chain_manager.clear() return chain_manager.get_df() gr.on(fn=chain_manager.get_df, outputs=df) df.change( fn=chain_manager.save, inputs=export_preset_name, outputs=export_btn ) export_preset_name.change( fn=chain_manager.save, inputs=export_preset_name, outputs=export_btn ) @import_btn.upload(inputs=import_btn, outputs=df) def load_ensemble_preset(filepath): chain_manager.load(filepath) return chain_manager.get_df() with gr.Row(): with gr.Column(): gr.Markdown("

Входное аудио

") with gr.Group(): with gr.Group(visible=False) as add_inputs: input_path = gr.Textbox( label="Путь к входному файлу", interactive=True ) add_inputs_btn = gr.Button("Загрузить файл", variant="primary") with gr.Group(visible=False) as add_inputs_from_url: input_url = gr.Textbox( label="URL входного файла", interactive=True ) with gr.Row(equal_height=True): inputs_url_format = gr.Dropdown( label="Формат входного файла", interactive=True, choices=self.audio.output_formats, value="mp3", filterable=False, ) inputs_url_bitrate = gr.Slider( label="Битрейт входного файла", minimum=64, maximum=512, step=32, value=320, interactive=True, ) with gr.Row(equal_height=True): inputs_url_cookie = gr.UploadButton( label="Файл cookie (необязательно)", interactive=True, type="filepath", file_count="single", file_types=[".txt", ".cookies"], variant="secondary", ) add_inputs_url_btn = gr.Button( "Загрузить файл", variant="primary" ) with gr.Row(visible=True, equal_height=True) as add_buttons_row: add_path_btn = gr.Button( "Загрузить файл по пути", variant="secondary" ) add_url_btn = gr.Button( "Загрузить файл по URL", variant="secondary" ) with gr.Group(): input_audio = gr.File( label="Входное аудио", interactive=True, type="filepath", file_count="single", file_types=[f".{of}" for of in self.audio.input_formats], ) with gr.Column(): gr.Markdown("

Настройки

") with gr.Group(): save_only_last_intermediate_stem_check = gr.Checkbox( label="Сохранить только последний промежуточный стем", interactive=True, value=False, ) output_format = gr.Dropdown( label="Формат выходного файла", interactive=True, choices=self.audio.output_formats, value="mp3", filterable=False, ) run_btn = gr.Button( "Создать цепочку разделений", variant="primary", interactive=True, ) with gr.Row(): with gr.Column(): gr.Markdown("

Результаты

") last_intermediate_stem = gr.Audio( label="Последний промежуточный стем", type="filepath", interactive=False, show_download_button=True, ) with gr.Group(): invert_method = gr.Radio( choices=["waveform", "spectrogram"], label="Метод создания инверсии", value="waveform", ) invert_btn = gr.Button("Инвертировать") output_inverted_audio = gr.Audio( label="Инверсия", type="filepath", interactive=False, show_download_button=True, ) @invert_btn.click( inputs=[ input_audio, last_intermediate_stem, invert_method, output_format, ], outputs=[output_inverted_audio], ) def invert_result_ensemble(input_file, output_file, method, out_format): if input_file and output_file: o_dir = os.path.dirname(output_file) basename = os.path.splitext(os.path.basename(input_file))[0] output_path = os.path.join( o_dir, f"chainless_{self.namer.short(basename, length=50)}_{method}_invert.{out_format}", ) inverted = self.inverter.process_audio( audio1_path=input_file, audio2_path=output_file, out_format=out_format, method=method, output_path=output_path, ) return inverted else: return None with gr.Column(): gr.Markdown("

Исходники цепочки

") output_source_files = gr.Files( type="filepath", interactive=False, show_label=False ) output_source_preview_check = gr.Checkbox( label="Показать плееры для исходников цепочки", interactive=True, value=False, ) @gr.render(inputs=[output_source_preview_check, output_source_files]) def show_output_auto_ensemble_players(preview, audios): if preview: if audios: with gr.Group(): for file in audios: gr.Audio( label=os.path.splitext(os.path.basename(file))[ 0 ], value=file, interactive=False, show_download_button=False, type="filepath", ) @run_btn.click( inputs=[input_audio, output_format, save_only_last_intermediate_stem_check], outputs=[last_intermediate_stem, output_source_files], ) def chain( input_audio, out_format, save_only_last_intermediate_stem, progress=gr.Progress(track_tqdm=True), ): input_settings = chain_manager.data timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") o = tempfile.mkdtemp(prefix=f"chainless_outputs_{timestamp}_") os.makedirs(o, exist_ok=True) base_name = os.path.splitext(os.path.basename(input_audio))[0] last_intermediate_stem = None last_intermediate_stem_name = None _output_stems = [] block_count = len(input_settings) for i, model in enumerate(input_settings, start=1): input_model_type = model[0] input_model_name = model[1] selected_stems = model[2] selected_output_stems = model[3] intermediate_stem = model[4] output_p = self.separate( input=( input_audio if not last_intermediate_stem else last_intermediate_stem ), output_dir=os.path.join(o, input_model_name), model_type=input_model_type, model_name=input_model_name, ext_inst=True, output_format=out_format, template=f"NAME_{i}_{f'({i - 1}_{str(last_intermediate_stem_name)})_' if last_intermediate_stem_name else ''}MODEL_STEM", selected_stems=selected_stems, add_settings={ "add_single_sep_text_progress": f"{i} из {block_count}" }, progress=progress, ) for stem, file in output_p: if stem in selected_output_stems: _output_stems.append(file) elif stem == intermediate_stem: last_intermediate_stem = file last_intermediate_stem_name = stem _output_stems.append(file) return last_intermediate_stem, ( _output_stems if not save_only_last_intermediate_stem else [] ) @add_inputs_btn.click( inputs=[input_path, input_audio], outputs=[add_inputs, input_audio, add_buttons_row], ) def add_inputs_fn(input_p, input_a): if input_p and os.path.exists(input_p): if input_a is None: input_a = None if self.audio.check(input_p): input_a = input_p return ( gr.update(visible=False), gr.update(value=input_a), gr.update(visible=True), ) return ( gr.update(visible=False), gr.update(value=input_a), gr.update(visible=True), ) @add_inputs_url_btn.click( inputs=[ input_url, input_audio, inputs_url_format, inputs_url_bitrate, inputs_url_cookie, ], outputs=[add_inputs_from_url, input_audio, add_buttons_row], ) def add_inputs_from_url_fn(input_u, input_a, fmt, br, cookie): if input_u: if input_a is None: input_a = None downloaded_file = dw_yt_dlp( url=input_u, output_format=fmt, output_bitrate=str(int(br)), cookie=cookie, ) if downloaded_file and os.path.exists(downloaded_file): if self.audio.check(downloaded_file): input_a = downloaded_file return ( gr.update(visible=False), gr.update(value=input_a), gr.update(visible=True), ) return ( gr.update(visible=False), gr.update(value=input_a), gr.update(visible=True), ) add_path_btn.click( lambda: (gr.update(visible=True), gr.update(visible=False)), outputs=[add_inputs, add_buttons_row], ) add_url_btn.click( lambda: (gr.update(visible=True), gr.update(visible=False)), outputs=[add_inputs_from_url, add_buttons_row], ) inputs_url_format.change( lambda x: gr.update( visible=False if x in ["wav", "flac", "aiff"] else True ), inputs=inputs_url_format, outputs=inputs_url_bitrate, )