diff --git a/README.md b/README.md index 1bdaf0fe..125b2b46 100644 --- a/README.md +++ b/README.md @@ -37,23 +37,13 @@ For information on installation, configuration, and usage, please visit our [doc Please see [this guide](https://docs.framepackstudio.com/docs/get_started/) on our documentation site to get FP-Studio installed. -## LoRAs +## Contributing -Add LoRAs to the /loras/ folder at the root of the installation. Select the LoRAs you wish to load and set the weights for each generation. Most Hunyuan LoRAs were originally trained for T2V, it's often helpful to run a T2V generation to ensure they're working before using input images. +We would love your help building FramePack Studio! To make collaboration effective, please adhere to the following: +- Keep Pull Requests Focused: Each Pull Request should address a single issue or add one specific feature. Please do not mix bug fixes, new features, and code refactoring in the same PR. +- Target the develop Branch: All Pull Requests must be opened against the develop branch. PRs opened against the main branch will be closed. +- Discuss Big Changes First: If you plan to work on a large feature or a significant refactor, please announce it first in the #contributors channel on our [Discord server](https://discord.com/invite/MtuM7gFJ3V). This helps us coordinate efforts and prevent duplicate work. -NOTE: Slow lora loading is a known issue - -## Working with Timestamped Prompts - -You can create videos with changing prompts over time using the following syntax: - -``` -[0s: A serene forest with sunlight filtering through the trees ] -[5s: A deer appears in the clearing ] -[10s: The deer drinks from a small stream ] -``` - -Each timestamp defines when that prompt should start influencing the generation. The system will (hopefully) smoothly transition between prompts for a cohesive video. ## Credits diff --git a/modules/MMAudio/app.py b/modules/MMAudio/app.py new file mode 100644 index 00000000..32a09140 --- /dev/null +++ b/modules/MMAudio/app.py @@ -0,0 +1,114 @@ +from typing import Optional +from pathlib import Path + +import torch + +from modules.MMAudio.mmaudio.eval_utils import ( + ModelConfig, + all_model_cfg, + generate as mmaudio_generate, + load_video, + make_video, +) +from mmaudio.model.flow_matching import FlowMatching +from mmaudio.model.networks import MMAudio, get_my_mmaudio +from mmaudio.model.sequence_config import SequenceConfig +from mmaudio.model.utils.features_utils import FeaturesUtils + +DEFAULT_AUDIO_NEGATIVE_PROMPT: list[str] = [ + 'music', + 'noise', +] + +# MMAudio Settings +torch.backends.cuda.matmul.allow_tf32 = True +torch.backends.cudnn.allow_tf32 = True +device = 'cuda' +dtype = torch.bfloat16 + +# Initialize MMAudio Model +def get_mmaudio_model(audio_model_config: Optional[ModelConfig] = None) -> tuple[MMAudio, FeaturesUtils, SequenceConfig]: + if audio_model_config is None: + audio_model_config = all_model_cfg['large_44k_v2'] + + audio_model_config.download_if_needed() + + seq_cfg = audio_model_config.seq_cfg + + net: MMAudio = get_my_mmaudio(audio_model_config.model_name).to(device, dtype).eval() + net.load_weights(torch.load(audio_model_config.model_path, map_location=device, weights_only=True)) + + feature_utils = FeaturesUtils(tod_vae_ckpt=audio_model_config.vae_path, + synchformer_ckpt=audio_model_config.synchformer_ckpt, + enable_conditions=True, + mode=audio_model_config.mode, + bigvgan_vocoder_ckpt=audio_model_config.bigvgan_16k_path, + need_vae_encoder=False) + feature_utils = feature_utils.to(device, dtype).eval() + + return net, feature_utils, seq_cfg + + +# Audio generation function +@torch.inference_mode() +def add_audio_to_video( + video_path: Path, + prompt: str, + audio_negative_prompt: str, + audio_steps: int, + audio_cfg_strength: int, + duration: int, + audio_net: MMAudio, + audio_feature_utils: FeaturesUtils, + audio_seq_cfg: SequenceConfig, + overwrite_orig_file: bool +) -> Path: + """Generate and add audio to video using MMAudio""" + + try: + rng = torch.Generator(device=device) + rng.seed() # Random seed for audio + fm = FlowMatching(min_sigma=0, inference_mode='euler', num_steps=audio_steps) + + video_info = load_video(video_path, duration) + clip_frames = video_info.clip_frames + sync_frames = video_info.sync_frames + duration = video_info.duration_sec + clip_frames = clip_frames.unsqueeze(0) + sync_frames = sync_frames.unsqueeze(0) + audio_seq_cfg.duration = duration + audio_net.update_seq_lengths(audio_seq_cfg.latent_seq_len, audio_seq_cfg.clip_seq_len, + audio_seq_cfg.sync_seq_len) + + audios = mmaudio_generate(clip_frames, sync_frames, + text=[prompt], + negative_text=[audio_negative_prompt], + feature_utils=audio_feature_utils, + net=audio_net, + fm=fm, + rng=rng, + cfg_strength=audio_cfg_strength) + audio = audios.float().cpu()[0] + + video_filename = video_path.name + + if overwrite_orig_file: + # Create video with audio, in the same location as the original file + video_with_audio_filename = video_filename + else: + # Create video with audio, in the same folder with the original video + video_with_audio_filename = video_filename + "_audio.mp4" + + video_dir = video_path.parent + video_with_audio_path = Path(video_dir) / video_with_audio_filename + + # video_with_audio_str = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4').name + # video_with_audio_path = Path(video_with_audio_str) + + # add a 'h264' video encoded stream and a 'aac' encoded audio stream, to the file 'video_with_audio_path' + make_video(video_info, video_with_audio_path, audio, sampling_rate=audio_seq_cfg.sampling_rate) + + return video_with_audio_path + except Exception as e: + print(f"Error in audio generation: {e}") + return video_path diff --git a/modules/MMAudio/mmaudio/data/av_utils.py b/modules/MMAudio/mmaudio/data/av_utils.py index e4998d9f..70cb056f 100644 --- a/modules/MMAudio/mmaudio/data/av_utils.py +++ b/modules/MMAudio/mmaudio/data/av_utils.py @@ -122,7 +122,27 @@ def reencode_with_audio(video_info: VideoInfo, output_path: Path, audio: torch.T container.mux(packet) container.close() + +def reencode_without_audio(video_info: VideoInfo, output_path: Path): + container = av.open(output_path, 'w') + output_video_stream = container.add_stream('h264', video_info.fps) + output_video_stream.codec_context.bit_rate = 10 * 1e6 # 10 Mbps + output_video_stream.width = video_info.width + output_video_stream.height = video_info.height + output_video_stream.pix_fmt = 'yuv420p' + + # encode video + for image in video_info.all_frames: + image = av.VideoFrame.from_ndarray(image) + packet = output_video_stream.encode(image) + container.mux(packet) + + for packet in output_video_stream.encode(): + container.mux(packet) + + container.close() + def remux_with_audio(video_path: Path, audio: torch.Tensor, output_path: Path, sampling_rate: int): """ diff --git a/modules/MMAudio/requirements.txt b/modules/MMAudio/requirements.txt new file mode 100644 index 00000000..bb01aa76 --- /dev/null +++ b/modules/MMAudio/requirements.txt @@ -0,0 +1,31 @@ +transformers +accelerate +safetensors +sentencepiece +peft +ftfy +imageio-ffmpeg +opencv-python + +python-dotenv +cython +gitpython >= 3.1 +tensorboard >= 2.11 +numpy >= 1.21, <2.1 +Pillow >= 9.5 +scipy >= 1.7 +tqdm >= 4.66.1 +gradio >= 3.34 +einops >= 0.6 +hydra-core >= 1.3.2 +requests +torchdiffeq +librosa >= 0.8.1 +nitrous-ema +auraloss +hydra_colorlog +tensordict +colorlog +open_clip_torch +soundfile +av diff --git a/modules/interface.py b/modules/interface.py index 5a1e34a0..5aaf2a53 100644 --- a/modules/interface.py +++ b/modules/interface.py @@ -337,7 +337,9 @@ def apply_startup_settings(): def refresh_loras(current_selected): if enumerate_lora_dir_fn: new_lora_names = enumerate_lora_dir_fn() - preserved = [name for name in (current_selected or []) if name in new_lora_names] + preserved = [ + name for name in (current_selected or []) if name in new_lora_names + ] return gr.update(choices=new_lora_names, value=preserved) return gr.update() diff --git a/modules/ui/generate.py b/modules/ui/generate.py index b619eba1..3773ec45 100644 --- a/modules/ui/generate.py +++ b/modules/ui/generate.py @@ -616,10 +616,14 @@ def process_with_queue_update(model_type_arg, *args): # After refreshing available LoRAs, choices can change, but we must keep lora_loaded_names (state) # aligned with the slider input order to avoid mixing/misalignment of weights. stable_slider_order = list(g["lora_sliders"].keys()) - incoming_weight_by_name = dict(zip(stable_slider_order, lora_slider_values_tuple)) + incoming_weight_by_name = dict( + zip(stable_slider_order, lora_slider_values_tuple) + ) # Override the lora_names_states and weights passed to the backend to match the stable slider order lora_names_states_arg = stable_slider_order - lora_slider_values_tuple = [incoming_weight_by_name.get(name, 1.0) for name in stable_slider_order] + lora_slider_values_tuple = [ + incoming_weight_by_name.get(name, 1.0) for name in stable_slider_order + ] result = f["process_fn"]( backend_model_type, diff --git a/modules/ui/outputs.py b/modules/ui/outputs.py index 7355bfa0..8904453f 100644 --- a/modules/ui/outputs.py +++ b/modules/ui/outputs.py @@ -1,8 +1,16 @@ +from typing import List, Any +from pathlib import Path import gradio as gr import os import json import logging +from modules.ui.audio import _ensure_mmaudio_on_path + +_ensure_mmaudio_on_path() + +from mmaudio.eval_utils import all_model_cfg # noqa: E402 + logger = logging.getLogger(__name__) @@ -26,7 +34,33 @@ def create_outputs_ui(settings): columns=[4], allow_preview=False, object_fit="cover", height="auto" ) refresh_gallery_button = gr.Button("🔄 Update Gallery") - delete_button = gr.Button("🗑️ Delete Selected", visible=False) + delete_btn = gr.Button("🗑️ Delete Selected", visible=False) + + gen_audio_acc = gr.Accordion(label="Audio", open=False, visible=False) + with gen_audio_acc: + with gr.Accordion(label="Generate"): + audio_model_dropdown = gr.Dropdown( + label="Select mmaudio model", + choices=all_model_cfg.keys(), + multiselect=False, + value="large_44k_v2", + info="Select one mmaudio model to use, to generate audio", + # scale=1, + ) + overwrite_audio_chkbox = gr.Checkbox( + label="Overwrite audio", visible=False + ) + + with gr.Blocks(): + audio_prompt_chkbox = gr.Checkbox(label="Append") + audio_prompt_txt = gr.Textbox(label="Positive prompt") + + with gr.Blocks(): + audio_prompt_neg_chkbox = gr.Checkbox(label="Append") + audio_prompt_neg_txt = gr.Textbox(label="Negative prompt") + + gen_audio_btn = gr.Button("Generate audio") + audio_delete_btn = gr.Button("🗑️ Delete") with gr.Column(scale=5): video_out = gr.Video(sources=[], autoplay=True, loop=True, visible=False) with gr.Column(scale=1): @@ -43,13 +77,24 @@ def create_outputs_ui(settings): "send_to_toolbox_btn": send_to_toolbox_btn, "outputDirectory_video": outputDirectory_video, "outputDirectory_metadata": outputDirectory_metadata, - "delete_button": delete_button, - "delete_selected_prefix_state": gr.State(None), + "delete_btn": delete_btn, + "selected_prefix_state": gr.State(None), + "gen_audio_btn": gen_audio_btn, + "overwrite_audio_chkbox": overwrite_audio_chkbox, + "gen_audio_acc": gen_audio_acc, + "audio_prompt_txt": audio_prompt_txt, + "audio_prompt_neg_txt": audio_prompt_neg_txt, + "audio_delete_btn": audio_delete_btn, + "audio_model_dropdown": audio_model_dropdown, + "audio_prompt_chkbox": audio_prompt_chkbox, + "audio_prompt_neg_chkbox": audio_prompt_neg_chkbox, } -def connect_outputs_events(o, tb_target_video_input, main_tabs_component): - def get_gallery_items(): +def connect_outputs_events( + o, tb_target_video_input: gr.Tab, main_tabs_component: gr.Tabs +): + def get_gallery_items() -> List[tuple[str, str]]: if not os.path.exists(o["outputDirectory_metadata"]): logging.error( f"Error: Metadata directory not found at {o['outputDirectory_metadata']}" @@ -85,11 +130,14 @@ def get_gallery_items(): return [(thumb, prefix) for thumb, prefix, _ in files_with_mtime] - def refresh_gallery(): + def refresh_gallery() -> tuple[gr.State, gr.Gallery]: new_items = get_gallery_items() - return new_items, gr.update(value=[item[0] for item in new_items]) + return ( + new_items, # gallery_items_state + gr.update(value=[item[0] for item in new_items]), # thumbs + ) - def get_latest_video_version(prefix): + def get_latest_video_version(prefix) -> str: max_number = -1 selected_file = None for f in os.listdir(o["outputDirectory_video"]): @@ -107,7 +155,9 @@ def get_latest_video_version(prefix): continue return selected_file - def load_video_and_info_from_prefix(prefix): + def load_video_and_info_from_prefix( + prefix, + ) -> tuple[None, str, gr.Button, None] | tuple[str, str, gr.Button, str]: video_file = get_latest_video_version(prefix) if not video_file: video_file = f"{prefix}.mp4" @@ -128,14 +178,17 @@ def load_video_and_info_from_prefix(prefix): video_path, ) - def delete_selected_item(selected_prefix): + def delete_selected_item( + selected_prefix: str, + ) -> tuple[gr.Video, gr.State, List, gr.Gallery, gr.Button, gr.Accordion]: if not selected_prefix: return ( - gr.update(visible=False), - None, - [], - gr.update(value=[]), - gr.update(visible=False), + gr.update(visible=False), # video out + None, # selected_original_video_path_state + [], # gallery_items_state + gr.update(value=[]), # thumbs + gr.update(visible=False), # delete button + gr.update(visible=False, open=False), # generate audio accordion ) deleted_files = [] @@ -148,7 +201,7 @@ def delete_selected_item(selected_prefix): os.remove(file_path) deleted_files.append(filename) except Exception as e: - print(f"Error deleting {filename}: {e}") + logger.error(f"Error deleting {filename}: {e}") # Also delete the base prefix.mp4 if it exists base_file = f"{selected_prefix}.mp4" @@ -158,7 +211,7 @@ def delete_selected_item(selected_prefix): os.remove(base_path) deleted_files.append(base_file) except Exception as e: - print(f"Error deleting {base_file}: {e}") + logger.error(f"Error deleting {base_file}: {e}") # Delete metadata files (json and png) for ext in [".json", ".png"]: @@ -169,29 +222,45 @@ def delete_selected_item(selected_prefix): os.remove(meta_path) deleted_files.append(meta_file) except Exception as e: - print(f"Error deleting {meta_file}: {e}") + logger.error(f"Error deleting {meta_file}: {e}") print(f"Deleted files: {deleted_files}") # Refresh the gallery new_items = get_gallery_items() return ( - gr.update(visible=False), - None, - new_items, - gr.update(value=[item[0] for item in new_items]), - gr.update(visible=False), + gr.update(visible=False), # video out + None, # selected_original_video_path_state + new_items, # gallery_items_state + gr.update(value=[item[0] for item in new_items]), # thumbs + gr.update(visible=False), # delete button + gr.update( + visible=False, open=False + ), # Make generate audio accordion invisible ) - def on_select(gallery_items, evt: gr.SelectData): + def on_select( + gallery_items, evt: gr.SelectData + ) -> tuple[ + gr.Video, + gr.Textbox, + gr.Button, + gr.State, + gr.Button, + gr.State, + gr.Accordion, + gr.Checkbox, + ]: if evt.index is None or not gallery_items or evt.index >= len(gallery_items): return ( - gr.update(visible=False), - gr.update(visible=False), - gr.update(visible=False), - None, - gr.update(visible=False), - None, # Return None for selected prefix + gr.update(visible=False), # video_out + gr.update(visible=False), # info_out + gr.update(visible=False), # send_to_toolbox_btn + None, # selected_original_video_path_state + gr.update(visible=False), # delete_btn + None, # selected_prefix_state + gr.update(visible=False), # gen_audio_acc + gr.update(visible=False), # overwrite_audio_chkbox ) prefix = gallery_items[evt.index][1] @@ -199,18 +268,201 @@ def on_select(gallery_items, evt: gr.SelectData): load_video_and_info_from_prefix(prefix) ) + overwrite_audio_checkbox_visibility = check_audio(prefix)[0] + return ( - gr.update(value=original_video_path, visible=bool(original_video_path)), - gr.update(value=info_string, visible=bool(original_video_path)), - gr.update(visible=bool(original_video_path)), - new_selected_path, - gr.update(visible=bool(original_video_path)), - prefix, # Return the selected prefix + gr.update( + value=original_video_path, visible=bool(original_video_path) + ), # video_out + gr.update(value=info_string, visible=bool(original_video_path)), # info_out + gr.update(visible=bool(original_video_path)), # send_to_toolbox_btn + new_selected_path, # selected_original_video_path_state + gr.update(visible=bool(original_video_path)), # delete_btn + prefix, # selected_prefix_state + gr.update(visible=bool(original_video_path)), # gen_audio_acc + overwrite_audio_checkbox_visibility, # overwrite_audio_chkbox ) - def send_to_toolbox(selected_video_path): + def send_to_toolbox(selected_video_path) -> tuple[gr.Tab, gr.Tabs]: return gr.update(value=selected_video_path), gr.update(selected="toolbox_tab") + def get_video_file_and_metadata(prefix) -> tuple[Path, str]: + original_video_path, metadata, button_visibility, new_selected_path = ( + load_video_and_info_from_prefix(prefix) + ) + if original_video_path is None: + logging.error(f"Video file and metadata for prefix {prefix}, not found") + return Path(original_video_path), metadata + + def get_audio(selected_prefix: str) -> tuple[Path, Any, int]: + video_file_path = get_video_file_and_metadata(selected_prefix)[0] + + from moviepy import VideoFileClip + + video_clip = VideoFileClip(video_file_path) + + # get the audio from the video + video_audio = video_clip.audio + + # get the duration of the video + video_duration = video_clip.duration + + return ( + video_file_path, + video_audio, + video_duration, + ) + + def generate_audio( + selected_prefix: str, + audio_model_dropdown: gr.Dropdown, + overwrite_audio_chkbox: gr.Checkbox, + audio_prompt_txt: gr.Textbox, + audio_prompt_neg_txt: gr.Textbox, + audio_prompt_chkbox: gr.Checkbox, + audio_prompt_neg_chkbox: gr.Checkbox, + ) -> tuple[gr.State]: + if not selected_prefix: + return ( + None, # selected_original_video_path_state + ) + + video_file, metadata = get_video_file_and_metadata(selected_prefix) + # load the metadata as a json + metadata = json.loads(metadata) + + video_file_path, video_audio, duration = get_audio(selected_prefix) + + logger.info("GENAudio: Loading video") + + if video_audio is not None and overwrite_audio_chkbox is False: + logging.info( + "GENAudio: Audio exists but overwrite audio is unchecked -> skipping" + ) + return ( + None, # selected_original_video_path_state + ) + + def get_prompt(p: str, b: str) -> str: + if p == "": + return b + if b != "": + return b + return p + + prompt = metadata.get("prompt", "") + + if audio_prompt_chkbox: + prompt += audio_prompt_txt + else: + prompt = get_prompt(metadata.get("prompt", ""), audio_prompt_txt) + + negative_prompt = metadata.get("negative_prompt", "") + + if audio_prompt_neg_chkbox: + negative_prompt += audio_prompt_neg_txt + else: + negative_prompt = get_prompt( + metadata.get("negative_prompt", ""), audio_prompt_neg_txt + ) + + # Append missing words from the DEFAULT_AUDIO_NEGATIVE_PROMPT list to the negative prompt + from modules.MMAudio.app import DEFAULT_AUDIO_NEGATIVE_PROMPT + + missing_words = [ + word + for word in DEFAULT_AUDIO_NEGATIVE_PROMPT + if word not in negative_prompt + ] + if missing_words: + if not negative_prompt.endswith(","): + negative_prompt += ", " + negative_prompt += ", ".join(missing_words) + + steps = metadata.get("steps", 25) + cfg_strength = metadata.get("cfg", 1) + + model_config = None + + if audio_model_dropdown: + model_config = all_model_cfg.get(audio_model_dropdown) + + logger.info( + f"GENAudio: Generating audio with\n" + f" prompt:{str(prompt)}\n" + f" negative prompt:{str(negative_prompt)}\n" + f" steps:{str(steps)}\n" + f" cfg:{str(cfg_strength)}" + ) + + from modules.MMAudio.app import get_mmaudio_model, add_audio_to_video + + audio_net_model, features_utils, sequence_config = get_mmaudio_model( + model_config + ) + video_with_audio_path = add_audio_to_video( + video_path=video_file, + prompt=prompt, + audio_negative_prompt=negative_prompt, + audio_steps=steps, + audio_cfg_strength=cfg_strength, + duration=duration, + audio_net=audio_net_model, + audio_feature_utils=features_utils, + audio_seq_cfg=sequence_config, + overwrite_orig_file=True, + ) + + if video_with_audio_path is not None: + logger.info( + "Generating audio finished for video: " + video_with_audio_path.name + ) + else: + logger.info("Error generating audio for video: " + video_file.name) + + return ( + None, # selected_original_video_path_state + ) + + def delete_audio(selected_prefix: str): + if not selected_prefix: + return + + video_file_path, video_audio, duration = get_audio(selected_prefix) + + if video_audio is not None: + from mmaudio.eval_utils import load_video + + video_info = load_video(video_file_path, duration) + + from mmaudio.data.av_utils import reencode_without_audio + + reencode_without_audio(video_info, video_file_path) + + logger.info(f"Deleted audio for video {str(video_file_path)}") + else: + logger.info(f"No audio found for video {str(video_file_path)}") + + def check_audio(selected_prefix: str) -> tuple[gr.Checkbox]: + if not selected_prefix: + return ( + gr.update(visible=False), # overwrite_audio_chkbox + ) + + try: + video_file_path, video_audio, duration = get_audio(selected_prefix) + if video_audio is not None: + return ( + gr.update(visible=True, value=False), # overwrite_audio_chkbox + ) + else: + return ( + gr.update(visible=False), # overwrite_audio_chkbox + ) + except Exception as e: + logger.error(f"Error checking audio: {e}") + return (gr.update(visible=False),) + o["refresh_gallery_button"].click( fn=refresh_gallery, inputs=[], outputs=[o["gallery_items_state"], o["thumbs"]] ) @@ -222,8 +474,10 @@ def send_to_toolbox(selected_video_path): o["info_out"], o["send_to_toolbox_btn"], o["selected_original_video_path_state"], - o["delete_button"], - o["delete_selected_prefix_state"], + o["delete_btn"], + o["selected_prefix_state"], + o["gen_audio_acc"], + o["overwrite_audio_chkbox"], ], ) o["send_to_toolbox_btn"].click( @@ -231,16 +485,47 @@ def send_to_toolbox(selected_video_path): inputs=[o["selected_original_video_path_state"]], outputs=[tb_target_video_input, main_tabs_component], ) - o["delete_button"].click( + o["delete_btn"].click( fn=delete_selected_item, - inputs=[o["delete_selected_prefix_state"]], + inputs=[o["selected_prefix_state"]], outputs=[ o["video_out"], o["selected_original_video_path_state"], o["gallery_items_state"], o["thumbs"], - o["delete_button"], + o["delete_btn"], + o["gen_audio_acc"], + ], + ) + o["gen_audio_btn"].click( + fn=generate_audio, + inputs=[ + o["selected_prefix_state"], + o["audio_model_dropdown"], + o["overwrite_audio_chkbox"], + o["audio_prompt_txt"], + o["audio_prompt_neg_txt"], + o["audio_prompt_chkbox"], + o["audio_prompt_neg_chkbox"], + ], + outputs=[ + o["selected_original_video_path_state"], + ], + ) + o["audio_delete_btn"].click( + fn=delete_audio, + inputs=[ + o["selected_prefix_state"], + ], + outputs=[], + ) + o["gen_audio_acc"].expand( + fn=check_audio, + inputs=[ + o["selected_prefix_state"], + ], + outputs=[ + o["overwrite_audio_chkbox"], ], ) - return get_gallery_items diff --git a/requirements.txt b/requirements.txt index b1bb156c..b0a13e87 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,36 +1,41 @@ -accelerate==1.6.0 -av==12.1.0 -decord -diffusers==0.33.1 -einops -ffmpeg-python==0.2.0 -gradio==5.25.2 -imageio-ffmpeg==0.4.8 -imageio==2.31.1 -jinja2>=3.1.2 -numpy==1.26.2 -opencv-contrib-python -peft -pillow==11.1.0 -requests==2.31.0 -safetensors -scipy==1.12.0 -sentencepiece==0.2.0 -torchsde==0.2.6 -tqdm -timm -transformers==4.46.2 - -# quantization -bitsandbytes>=0.41.1 - -# for toolbox -basicsr -# basicsr-fixed -devicetorch -facexlib>=0.2.5 -gfpgan>=1.3.5 -psutil -realesrgan -colorlog - +accelerate==1.6.0 +av==12.1.0 +decord +diffusers==0.33.1 +einops +ffmpeg-python==0.2.0 +gradio==5.25.2 +huggingface_hub<0.35.1 +imageio-ffmpeg==0.4.8 +imageio==2.31.1 +jinja2>=3.1.2 +numpy==1.26.2 +opencv-contrib-python +peft<0.18.0 +pillow==11.1.0 +requests==2.31.0 +safetensors +scipy==1.12.0 +sentencepiece==0.2.0 +torchsde==0.2.6 +tqdm +timm +transformers==4.46.2 + +# quantization +bitsandbytes>=0.41.1 + +# for toolbox +basicsr +# basicsr-fixed +devicetorch +facexlib>=0.2.5 +gfpgan>=1.3.5 +psutil +realesrgan +colorlog + + +# mmaudio +moviepy +-r ./modules/MMAudio/requirements.txt diff --git a/shared/Enums.py b/shared/Enums.py index 619794e2..b317c04e 100644 --- a/shared/Enums.py +++ b/shared/Enums.py @@ -14,7 +14,6 @@ class QuantizationFormat(StrEnum): NONE = brain_floating_point_16bit DEFAULT = NONE - @staticmethod def supported_values() -> list[str]: """Returns a list of all supported QuantizationFormat values.""" @@ -46,7 +45,6 @@ class LoraLoader(StrEnum): LORA_READY = "lora_ready" DEFAULT = LORA_READY - @staticmethod def supported_values() -> list[str]: """Returns a list of all supported LoraLoader values."""