| ''' |
| !huggingface-cli download \ |
| --repo-type dataset svjack/video-dataset-Lily-Bikini-rm-background-organized \ |
| --local-dir video-dataset-Lily-Bikini-rm-background-organized |
| |
| import re |
| |
| def insert_content_in_string(insert_content, character_name, gender=None): |
| """ |
| 在原始字符串中特定位置插入内容。 |
| |
| :param insert_content: 要插入的内容 |
| :param character_name: 角色名称 |
| :param gender: 性别(可选,可以是 "1boy" 或 "1girl") |
| :return: 修改后的字符串 |
| """ |
| # 根据 character_name 和 gender 生成 original_string |
| original_string = f"solo,{character_name}\(genshin impact\),{gender if gender else '1boy'},highres," |
| # 根据 character_name 生成 target_pattern |
| target_pattern = re.escape(character_name) |
| # 插入内容 |
| modified_string = re.sub(target_pattern, r'\g<0>' + insert_content, original_string) |
| return original_string ,modified_string |
| |
| from datasets import load_dataset |
| character_name = "Xiangling" |
| gender = "1girl" # 可选参数 |
| prompt_list = load_dataset("svjack/daily-actions-en-zh")["train"].to_pandas()["en"].map( |
| lambda x: ", {}".format(x) |
| ).map( |
| lambda insert_content: insert_content_in_string(insert_content, character_name, gender)[-1] |
| ).dropna().drop_duplicates().values.tolist() |
| print(len(prompt_list)) |
| |
| import pandas as pd |
| import pathlib |
| reference_video_list = pd.Series( |
| list(pathlib.Path("video-dataset-Lily-Bikini-rm-background-organized").rglob("*.mp4")) |
| ).map(str).values.tolist() |
| print(len(reference_video_list)) |
| |
| from itertools import product |
| pd.DataFrame(list(product(*[reference_video_list, prompt_list])))[[1, 0]].rename( |
| columns = { |
| 1: "prompt", |
| 0: "input_video" |
| } |
| ).to_csv("xiangling_video_seed.csv", index = False) |
| |
| !python produce_gif_script.py xiangling_video_seed.csv "svjack/GenshinImpact_XL_Base" xiangling_gif_dir \ |
| --num_frames 16 --temp_folder temp_frames --seed 0 --controlnet_conditioning_scale 0.3 |
| |
| !python Linfusion_upscale_video_folder.py xiangling_mp4 xiangling_mp4_upscaled \ |
| "solo,Xiangling,_genshin_impact_,1girl,highres" --upscale_factor 2 --upscale_strength 0.5 |
| |
| import re |
| def insert_content_in_string(insert_content, character_name, gender=None): |
| """ |
| 在原始字符串中特定位置插入内容。 |
| :param insert_content: 要插入的内容 |
| :param character_name: 角色名称 |
| :param gender: 性别(可选,可以是 "1boy" 或 "1girl") |
| :return: 修改后的字符串 |
| """ |
| # 根据 character_name 和 gender 生成 original_string |
| original_string = f"solo,{character_name}\(genshin impact\),{gender if gender else '1boy'},highres," |
| original_string = f"{gender if gender else '1boy'},{character_name}, masterpiece, white lab coat, red tie" |
| # 根据 character_name 生成 target_pattern |
| target_pattern = re.escape(character_name) |
| # 插入内容 |
| modified_string = re.sub(target_pattern, r'\g<0>' + insert_content, original_string) |
| return original_string ,modified_string |
| |
| from datasets import load_dataset |
| character_name = "Makise Kurisu" |
| gender = "1girl" # 可选参数 |
| prompt_list = load_dataset("svjack/daily-actions-en-zh")["train"].to_pandas()["en"].map( |
| lambda x: ", {}".format(x) |
| ).map( |
| lambda insert_content: insert_content_in_string(insert_content, character_name, gender)[-1] |
| ).dropna().drop_duplicates().values.tolist() |
| print(len(prompt_list)) |
| |
| import pandas as pd |
| import pathlib |
| reference_video_list = pd.Series( |
| list(pathlib.Path("video-dataset-Lily-Bikini-rm-background-organized").rglob("*.mp4")) |
| ).map(str).values.tolist() |
| print(len(reference_video_list)) |
| |
| from itertools import product |
| pd.DataFrame(list(product(*[reference_video_list, prompt_list])))[[1, 0]].rename( |
| columns = { |
| 1: "prompt", |
| 0: "input_video" |
| } |
| ).to_csv("Makise_Kurisu_video_seed.csv", index = False) |
| |
| !python produce_gif_script.py Makise_Kurisu_video_seed.csv "cagliostrolab/animagine-xl-3.1" Makise_Kurisu_gif_dir \ |
| --num_frames 16 --temp_folder temp_frames --seed 0 --controlnet_conditioning_scale 0.3 |
| |
| !python Linfusion_upscale_video_folder.py Makise_Kurisu_mp4 Makise_Kurisu_mp4_upscaled \ |
| "1girl, Makise Kurisu, masterpiece, white lab coat, red tie, laboratory" --upscale_factor 2 --upscale_strength 0.5 |
| ''' |
|
|
| import sys |
| sys.path.insert(0, "diffusers-sdxl-controlnet/examples/community/") |
| from animatediff_controlnet_sdxl import * |
|
|
| import argparse |
| from moviepy.editor import VideoFileClip, ImageSequenceClip |
| import os |
| import torch |
| from diffusers.models import MotionAdapter |
| from diffusers import DDIMScheduler, AutoPipelineForText2Image, ControlNetModel |
| from diffusers.utils import export_to_gif |
| from PIL import Image |
| from controlnet_aux.processor import Processor |
| import pandas as pd |
| import random |
| from tqdm import tqdm |
|
|
| |
| adapter = MotionAdapter.from_pretrained("a-r-r-o-w/animatediff-motion-adapter-sdxl-beta", torch_dtype=torch.float16) |
|
|
| def initialize_pipeline(model_id): |
| scheduler = DDIMScheduler.from_pretrained(model_id, subfolder="scheduler", clip_sample=False, timestep_spacing="linspace", beta_schedule="linear", steps_offset=1) |
| controlnet = ControlNetModel.from_pretrained("thibaud/controlnet-openpose-sdxl-1.0", torch_dtype=torch.float16).to("cuda") |
|
|
| |
| pipe = AnimateDiffSDXLControlnetPipeline.from_pretrained( |
| model_id, |
| controlnet=controlnet, |
| motion_adapter=adapter, |
| scheduler=scheduler, |
| torch_dtype=torch.float16, |
| ).to("cuda") |
| pipe.enable_vae_slicing() |
| pipe.enable_vae_tiling() |
| return pipe |
|
|
| |
| pipe = None |
|
|
| def split_video_into_frames(input_video_path, num_frames, temp_folder='temp_frames'): |
| """ |
| 将视频处理成指定帧数的视频,并保持原始的帧率。 |
| |
| :param input_video_path: 输入视频文件路径 |
| :param num_frames: 目标帧数 |
| :param temp_folder: 临时文件夹路径 |
| """ |
| clip = VideoFileClip(input_video_path) |
| original_duration = clip.duration |
| segment_duration = original_duration / num_frames |
|
|
| if not os.path.exists(temp_folder): |
| os.makedirs(temp_folder) |
|
|
| for i in range(num_frames): |
| frame_time = i * segment_duration |
| frame_path = os.path.join(temp_folder, f'frame_{i:04d}.png') |
| clip.save_frame(frame_path, t=frame_time) |
|
|
| frame_paths = [os.path.join(temp_folder, f'frame_{i:04d}.png') for i in range(num_frames)] |
| final_clip = ImageSequenceClip(frame_paths, fps=clip.fps) |
| final_clip.write_videofile("resampled_video.mp4", codec='libx264') |
|
|
| print(f"新的视频已保存到 resampled_video.mp4,包含 {num_frames} 个帧,并保持原始的帧率。") |
|
|
| def generate_video_with_prompt(input_video_path, prompt, model_id, gif_output_path, seed=0, num_frames=16, keep_imgs=False, temp_folder='temp_frames', num_inference_steps=50, guidance_scale=20, controlnet_conditioning_scale=0.5, width=512, height=768): |
| """ |
| 生成带有文本提示的视频。 |
| |
| :param input_video_path: 输入视频文件路径 |
| :param prompt: 文本提示 |
| :param model_id: 模型ID |
| :param gif_output_path: GIF 输出文件路径 |
| :param seed: 随机种子 |
| :param num_frames: 目标帧数 |
| :param keep_imgs: 是否保留临时图片 |
| :param temp_folder: 临时文件夹路径 |
| :param num_inference_steps: 推理步数 |
| :param guidance_scale: 引导比例 |
| :param controlnet_conditioning_scale: ControlNet 条件比例 |
| :param width: 输出宽度 |
| :param height: 输出高度 |
| """ |
| split_video_into_frames(input_video_path, num_frames, temp_folder) |
|
|
| folder_path = temp_folder |
| frames = os.listdir(folder_path) |
| frames = list(filter(lambda x: x.endswith(".png"), frames)) |
| frames.sort() |
| conditioning_frames = list(map(lambda x: Image.open(os.path.join(folder_path, x)).resize((1024, 1024)), frames))[:num_frames] |
|
|
| p2 = Processor("openpose") |
| cn2 = [p2(frame) for frame in conditioning_frames] |
|
|
| negative_prompt = "bad quality, worst quality, jpeg artifacts, ugly" |
| generator = torch.Generator(device="cuda").manual_seed(seed) |
|
|
| global pipe |
| if pipe is None: |
| pipe = initialize_pipeline(model_id) |
|
|
| output = pipe( |
| prompt=prompt, |
| negative_prompt=negative_prompt, |
| num_inference_steps=num_inference_steps, |
| guidance_scale=guidance_scale, |
| controlnet_conditioning_scale=controlnet_conditioning_scale, |
| width=width, |
| height=height, |
| num_frames=num_frames, |
| conditioning_frames=cn2, |
| generator=generator |
| ) |
|
|
| frames = output.frames[0] |
| export_to_gif(frames, gif_output_path) |
|
|
| print(f"生成的 GIF 已保存到 {gif_output_path}") |
|
|
| if not keep_imgs: |
| |
| import shutil |
| shutil.rmtree(temp_folder) |
|
|
| def sanitize_prompt(prompt): |
| """ |
| 将提示词中的空格和非英文字符替换为下划线。 |
| """ |
| return "".join([c if c.isalnum() or c in [",", ","] else '_' for c in prompt]) |
|
|
| if __name__ == "__main__": |
| parser = argparse.ArgumentParser(description="生成带有文本提示的视频") |
| parser.add_argument("csv_file", help="CSV 文件路径") |
| parser.add_argument("model_id", help="模型ID") |
| parser.add_argument("output_dir", help="GIF 输出目录") |
| parser.add_argument("--seed", type=int, default=0, help="随机种子") |
| parser.add_argument("--num_frames", type=int, default=16, help="目标帧数") |
| parser.add_argument("--keep_imgs", action="store_true", help="是否保留临时图片") |
| parser.add_argument("--temp_folder", default='temp_frames', help="临时文件夹路径") |
| parser.add_argument("--num_inference_steps", type=int, default=50, help="推理步数") |
| parser.add_argument("--guidance_scale", type=float, default=20.0, help="引导比例") |
| parser.add_argument("--controlnet_conditioning_scale", type=float, default=0.5, help="ControlNet 条件比例") |
| parser.add_argument("--width", type=int, default=512, help="输出宽度") |
| parser.add_argument("--height", type=int, default=768, help="输出高度") |
|
|
| args = parser.parse_args() |
|
|
| |
| df = pd.read_csv(args.csv_file) |
|
|
| for index, row in tqdm(df.iterrows(), total=df.shape[0]): |
| input_video = row['input_video'] |
| prompt = row['prompt'] |
|
|
| |
| seed = random.randint(0, 2**32 - 1) |
|
|
| |
| sanitized_prompt = sanitize_prompt(prompt) |
|
|
| |
| if not os.path.exists(args.output_dir): |
| os.makedirs(args.output_dir) |
| gif_output_path = os.path.join(args.output_dir, f"{sanitized_prompt}_seed_{seed}.gif") |
|
|
| generate_video_with_prompt(input_video, prompt, args.model_id, gif_output_path, seed, args.num_frames, |
| args.keep_imgs, args.temp_folder, args.num_inference_steps, args.guidance_scale, |
| args.controlnet_conditioning_scale, args.width, args.height) |