system1-developer/ltx-video-distilled
0
1import argparse2import os3import random4from datetime import datetime5from pathlib import Path6from diffusers.utils import logging7from typing import Optional, List, Union8import yaml9 10import imageio11import json12import numpy as np13import torch14import cv215from safetensors import safe_open16from PIL import Image17from transformers import (18 T5EncoderModel,19 T5Tokenizer,20 AutoModelForCausalLM,21 AutoProcessor,22 AutoTokenizer,23)24from huggingface_hub import hf_hub_download25 26from ltx_video.models.autoencoders.causal_video_autoencoder import (27 CausalVideoAutoencoder,28)29from ltx_video.models.transformers.symmetric_patchifier import SymmetricPatchifier30from ltx_video.models.transformers.transformer3d import Transformer3DModel31from ltx_video.pipelines.pipeline_ltx_video import (32 ConditioningItem,33 LTXVideoPipeline,34 LTXMultiScalePipeline,35)36from ltx_video.schedulers.rf import RectifiedFlowScheduler37from ltx_video.utils.skip_layer_strategy import SkipLayerStrategy38from ltx_video.models.autoencoders.latent_upsampler import LatentUpsampler39import ltx_video.pipelines.crf_compressor as crf_compressor40 41MAX_HEIGHT = 72042MAX_WIDTH = 128043MAX_NUM_FRAMES = 25744 45logger = logging.get_logger("LTX-Video")46 47 48def get_total_gpu_memory():49 if torch.cuda.is_available():50 total_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)51 return total_memory52 return 053 54 55def get_device():56 if torch.cuda.is_available():57 return "cuda"58 elif torch.backends.mps.is_available():59 return "mps"60 return "cpu"61 62 63def load_image_to_tensor_with_resize_and_crop(64 image_input: Union[str, Image.Image],65 target_height: int = 512,66 target_width: int = 768,67 just_crop: bool = False,68) -> torch.Tensor:69 """Load and process an image into a tensor.70 71 Args:72 image_input: Either a file path (str) or a PIL Image object73 target_height: Desired height of output tensor74 target_width: Desired width of output tensor75 just_crop: If True, only crop the image to the target size without resizing76 """77 if isinstance(image_input, str):78 image = Image.open(image_input).convert("RGB")79 elif isinstance(image_input, Image.Image):80 image = image_input81 else:82 raise ValueError("image_input must be either a file path or a PIL Image object")83 84 input_width, input_height = image.size85 aspect_ratio_target = target_width / target_height86 aspect_ratio_frame = input_width / input_height87 if aspect_ratio_frame > aspect_ratio_target:88 new_width = int(input_height * aspect_ratio_target)89 new_height = input_height90 x_start = (input_width - new_width) // 291 y_start = 092 else:93 new_width = input_width94 new_height = int(input_width / aspect_ratio_target)95 x_start = 096 y_start = (input_height - new_height) // 297 98 image = image.crop((x_start, y_start, x_start + new_width, y_start + new_height))99 if not just_crop:100 image = image.resize((target_width, target_height))101 102 image = np.array(image)103 image = cv2.GaussianBlur(image, (3, 3), 0)104 frame_tensor = torch.from_numpy(image).float()105 frame_tensor = crf_compressor.compress(frame_tensor / 255.0) * 255.0106 frame_tensor = frame_tensor.permute(2, 0, 1)107 frame_tensor = (frame_tensor / 127.5) - 1.0108 # Create 5D tensor: (batch_size=1, channels=3, num_frames=1, height, width)109 return frame_tensor.unsqueeze(0).unsqueeze(2)110 111 112def calculate_padding(113 source_height: int, source_width: int, target_height: int, target_width: int114) -> tuple[int, int, int, int]:115 116 # Calculate total padding needed117 pad_height = target_height - source_height118 pad_width = target_width - source_width119 120 # Calculate padding for each side121 pad_top = pad_height // 2122 pad_bottom = pad_height - pad_top # Handles odd padding123 pad_left = pad_width // 2124 pad_right = pad_width - pad_left # Handles odd padding125 126 # Return padded tensor127 # Padding format is (left, right, top, bottom)128 padding = (pad_left, pad_right, pad_top, pad_bottom)129 return padding130 131 132def convert_prompt_to_filename(text: str, max_len: int = 20) -> str:133 # Remove non-letters and convert to lowercase134 clean_text = "".join(135 char.lower() for char in text if char.isalpha() or char.isspace()136 )137 138 # Split into words139 words = clean_text.split()140 141 # Build result string keeping track of length142 result = []143 current_length = 0144 145 for word in words:146 # Add word length plus 1 for underscore (except for first word)147 new_length = current_length + len(word)148 149 if new_length <= max_len:150 result.append(word)151 current_length += len(word)152 else:153 break154 155 return "-".join(result)156 157 158# Generate output video name159def get_unique_filename(160 base: str,161 ext: str,162 prompt: str,163 seed: int,164 resolution: tuple[int, int, int],165 dir: Path,166 endswith=None,167 index_range=1000,168) -> Path:169 base_filename = f"{base}_{convert_prompt_to_filename(prompt, max_len=30)}_{seed}_{resolution[0]}x{resolution[1]}x{resolution[2]}"170 for i in range(index_range):171 filename = dir / f"{base_filename}_{i}{endswith if endswith else ''}{ext}"172 if not os.path.exists(filename):173 return filename174 raise FileExistsError(175 f"Could not find a unique filename after {index_range} attempts."176 )177 178 179def seed_everething(seed: int):180 random.seed(seed)181 np.random.seed(seed)182 torch.manual_seed(seed)183 if torch.cuda.is_available():184 torch.cuda.manual_seed(seed)185 if torch.backends.mps.is_available():186 torch.mps.manual_seed(seed)187 188 189def main():190 parser = argparse.ArgumentParser(191 description="Load models from separate directories and run the pipeline."192 )193 194 # Directories195 parser.add_argument(196 "--output_path",197 type=str,198 default=None,199 help="Path to the folder to save output video, if None will save in outputs/ directory.",200 )201 parser.add_argument("--seed", type=int, default="171198")202 203 # Pipeline parameters204 parser.add_argument(205 "--num_images_per_prompt",206 type=int,207 default=1,208 help="Number of images per prompt",209 )210 parser.add_argument(211 "--image_cond_noise_scale",212 type=float,213 default=0.15,214 help="Amount of noise to add to the conditioned image",215 )216 parser.add_argument(217 "--height",218 type=int,219 default=704,220 help="Height of the output video frames. Optional if an input image provided.",221 )222 parser.add_argument(223 "--width",224 type=int,225 default=1216,226 help="Width of the output video frames. If None will infer from input image.",227 )228 parser.add_argument(229 "--num_frames",230 type=int,231 default=121,232 help="Number of frames to generate in the output video",233 )234 parser.add_argument(235 "--frame_rate", type=int, default=30, help="Frame rate for the output video"236 )237 parser.add_argument(238 "--device",239 default=None,240 help="Device to run inference on. If not specified, will automatically detect and use CUDA or MPS if available, else CPU.",241 )242 parser.add_argument(243 "--pipeline_config",244 type=str,245 default="configs/ltxv-13b-0.9.7-dev.yaml",246 help="The path to the config file for the pipeline, which contains the parameters for the pipeline",247 )248 249 # Prompts250 parser.add_argument(251 "--prompt",252 type=str,253 help="Text prompt to guide generation",254 )255 parser.add_argument(256 "--negative_prompt",257 type=str,258 default="worst quality, inconsistent motion, blurry, jittery, distorted",259 help="Negative prompt for undesired features",260 )261 262 parser.add_argument(263 "--offload_to_cpu",264 action="store_true",265 help="Offloading unnecessary computations to CPU.",266 )267 268 # video-to-video arguments:269 parser.add_argument(270 "--input_media_path",271 type=str,272 default=None,273 help="Path to the input video (or imaage) to be modified using the video-to-video pipeline",274 )275 276 # Conditioning arguments277 parser.add_argument(278 "--conditioning_media_paths",279 type=str,280 nargs="*",281 help="List of paths to conditioning media (images or videos). Each path will be used as a conditioning item.",282 )283 parser.add_argument(284 "--conditioning_strengths",285 type=float,286 nargs="*",287 help="List of conditioning strengths (between 0 and 1) for each conditioning item. Must match the number of conditioning items.",288 )289 parser.add_argument(290 "--conditioning_start_frames",291 type=int,292 nargs="*",293 help="List of frame indices where each conditioning item should be applied. Must match the number of conditioning items.",294 )295 296 args = parser.parse_args()297 logger.warning(f"Running generation with arguments: {args}")298 infer(**vars(args))299 300 301def create_ltx_video_pipeline(302 ckpt_path: str,303 precision: str,304 text_encoder_model_name_or_path: str,305 sampler: Optional[str] = None,306 device: Optional[str] = None,307 enhance_prompt: bool = False,308 prompt_enhancer_image_caption_model_name_or_path: Optional[str] = None,309 prompt_enhancer_llm_model_name_or_path: Optional[str] = None,310) -> LTXVideoPipeline:311 ckpt_path = Path(ckpt_path)312 assert os.path.exists(313 ckpt_path314 ), f"Ckpt path provided (--ckpt_path) {ckpt_path} does not exist"315 316 with safe_open(ckpt_path, framework="pt") as f:317 metadata = f.metadata()318 config_str = metadata.get("config")319 configs = json.loads(config_str)320 allowed_inference_steps = configs.get("allowed_inference_steps", None)321 322 vae = CausalVideoAutoencoder.from_pretrained(ckpt_path)323 transformer = Transformer3DModel.from_pretrained(ckpt_path)324 325 # Use constructor if sampler is specified, otherwise use from_pretrained326 if sampler == "from_checkpoint" or not sampler:327 scheduler = RectifiedFlowScheduler.from_pretrained(ckpt_path)328 else:329 scheduler = RectifiedFlowScheduler(330 sampler=("Uniform" if sampler.lower() == "uniform" else "LinearQuadratic")331 )332 333 text_encoder = T5EncoderModel.from_pretrained(334 text_encoder_model_name_or_path, subfolder="text_encoder"335 )336 patchifier = SymmetricPatchifier(patch_size=1)337 tokenizer = T5Tokenizer.from_pretrained(338 text_encoder_model_name_or_path, subfolder="tokenizer"339 )340 341 transformer = transformer.to(device)342 vae = vae.to(device)343 text_encoder = text_encoder.to(device)344 345 if enhance_prompt:346 prompt_enhancer_image_caption_model = AutoModelForCausalLM.from_pretrained(347 prompt_enhancer_image_caption_model_name_or_path, trust_remote_code=True348 )349 prompt_enhancer_image_caption_processor = AutoProcessor.from_pretrained(350 prompt_enhancer_image_caption_model_name_or_path, trust_remote_code=True351 )352 prompt_enhancer_llm_model = AutoModelForCausalLM.from_pretrained(353 prompt_enhancer_llm_model_name_or_path,354 torch_dtype="bfloat16",355 )356 prompt_enhancer_llm_tokenizer = AutoTokenizer.from_pretrained(357 prompt_enhancer_llm_model_name_or_path,358 )359 else:360 prompt_enhancer_image_caption_model = None361 prompt_enhancer_image_caption_processor = None362 prompt_enhancer_llm_model = None363 prompt_enhancer_llm_tokenizer = None364 365 vae = vae.to(torch.bfloat16)366 if precision == "bfloat16" and transformer.dtype != torch.bfloat16:367 transformer = transformer.to(torch.bfloat16)368 text_encoder = text_encoder.to(torch.bfloat16)369 370 # Use submodels for the pipeline371 submodel_dict = {372 "transformer": transformer,373 "patchifier": patchifier,374 "text_encoder": text_encoder,375 "tokenizer": tokenizer,376 "scheduler": scheduler,377 "vae": vae,378 "prompt_enhancer_image_caption_model": prompt_enhancer_image_caption_model,379 "prompt_enhancer_image_caption_processor": prompt_enhancer_image_caption_processor,380 "prompt_enhancer_llm_model": prompt_enhancer_llm_model,381 "prompt_enhancer_llm_tokenizer": prompt_enhancer_llm_tokenizer,382 "allowed_inference_steps": allowed_inference_steps,383 }384 385 pipeline = LTXVideoPipeline(**submodel_dict)386 pipeline = pipeline.to(device)387 return pipeline388 389 390def create_latent_upsampler(latent_upsampler_model_path: str, device: str):391 latent_upsampler = LatentUpsampler.from_pretrained(latent_upsampler_model_path)392 latent_upsampler.to(device)393 latent_upsampler.eval()394 return latent_upsampler395 396 397def infer(398 output_path: Optional[str],399 seed: int,400 pipeline_config: str,401 image_cond_noise_scale: float,402 height: Optional[int],403 width: Optional[int],404 num_frames: int,405 frame_rate: int,406 prompt: str,407 negative_prompt: str,408 offload_to_cpu: bool,409 input_media_path: Optional[str] = None,410 conditioning_media_paths: Optional[List[str]] = None,411 conditioning_strengths: Optional[List[float]] = None,412 conditioning_start_frames: Optional[List[int]] = None,413 device: Optional[str] = None,414 **kwargs,415):416 # check if pipeline_config is a file417 if not os.path.isfile(pipeline_config):418 raise ValueError(f"Pipeline config file {pipeline_config} does not exist")419 with open(pipeline_config, "r") as f:420 pipeline_config = yaml.safe_load(f)421 422 models_dir = "MODEL_DIR"423 424 ltxv_model_name_or_path = pipeline_config["checkpoint_path"]425 if not os.path.isfile(ltxv_model_name_or_path):426 ltxv_model_path = hf_hub_download(427 repo_id="Lightricks/LTX-Video",428 filename=ltxv_model_name_or_path,429 local_dir=models_dir,430 repo_type="model",431 )432 else:433 ltxv_model_path = ltxv_model_name_or_path434 435 spatial_upscaler_model_name_or_path = pipeline_config.get(436 "spatial_upscaler_model_path"437 )438 if spatial_upscaler_model_name_or_path and not os.path.isfile(439 spatial_upscaler_model_name_or_path440 ):441 spatial_upscaler_model_path = hf_hub_download(442 repo_id="Lightricks/LTX-Video",443 filename=spatial_upscaler_model_name_or_path,444 local_dir=models_dir,445 repo_type="model",446 )447 else:448 spatial_upscaler_model_path = spatial_upscaler_model_name_or_path449 450 if kwargs.get("input_image_path", None):451 logger.warning(452 "Please use conditioning_media_paths instead of input_image_path."453 )454 assert not conditioning_media_paths and not conditioning_start_frames455 conditioning_media_paths = [kwargs["input_image_path"]]456 conditioning_start_frames = [0]457 458 # Validate conditioning arguments459 if conditioning_media_paths:460 # Use default strengths of 1.0461 if not conditioning_strengths:462 conditioning_strengths = [1.0] * len(conditioning_media_paths)463 if not conditioning_start_frames:464 raise ValueError(465 "If `conditioning_media_paths` is provided, "466 "`conditioning_start_frames` must also be provided"467 )468 if len(conditioning_media_paths) != len(conditioning_strengths) or len(469 conditioning_media_paths470 ) != len(conditioning_start_frames):471 raise ValueError(472 "`conditioning_media_paths`, `conditioning_strengths`, "473 "and `conditioning_start_frames` must have the same length"474 )475 if any(s < 0 or s > 1 for s in conditioning_strengths):476 raise ValueError("All conditioning strengths must be between 0 and 1")477 if any(f < 0 or f >= num_frames for f in conditioning_start_frames):478 raise ValueError(479 f"All conditioning start frames must be between 0 and {num_frames-1}"480 )481 482 seed_everething(seed)483 if offload_to_cpu and not torch.cuda.is_available():484 logger.warning(485 "offload_to_cpu is set to True, but offloading will not occur since the model is already running on CPU."486 )487 offload_to_cpu = False488 else:489 offload_to_cpu = offload_to_cpu and get_total_gpu_memory() < 30490 491 output_dir = (492 Path(output_path)493 if output_path494 else Path(f"outputs/{datetime.today().strftime('%Y-%m-%d')}")495 )496 output_dir.mkdir(parents=True, exist_ok=True)497 498 # Adjust dimensions to be divisible by 32 and num_frames to be (N * 8 + 1)499 height_padded = ((height - 1) // 32 + 1) * 32500 width_padded = ((width - 1) // 32 + 1) * 32501 num_frames_padded = ((num_frames - 2) // 8 + 1) * 8 + 1502 503 padding = calculate_padding(height, width, height_padded, width_padded)504 505 logger.warning(506 f"Padded dimensions: {height_padded}x{width_padded}x{num_frames_padded}"507 )508 509 prompt_enhancement_words_threshold = pipeline_config[510 "prompt_enhancement_words_threshold"511 ]512 513 prompt_word_count = len(prompt.split())514 enhance_prompt = (515 prompt_enhancement_words_threshold > 0516 and prompt_word_count < prompt_enhancement_words_threshold517 )518 519 if prompt_enhancement_words_threshold > 0 and not enhance_prompt:520 logger.info(521 f"Prompt has {prompt_word_count} words, which exceeds the threshold of {prompt_enhancement_words_threshold}. Prompt enhancement disabled."522 )523 524 precision = pipeline_config["precision"]525 text_encoder_model_name_or_path = pipeline_config["text_encoder_model_name_or_path"]526 sampler = pipeline_config["sampler"]527 prompt_enhancer_image_caption_model_name_or_path = pipeline_config[528 "prompt_enhancer_image_caption_model_name_or_path"529 ]530 prompt_enhancer_llm_model_name_or_path = pipeline_config[531 "prompt_enhancer_llm_model_name_or_path"532 ]533 534 pipeline = create_ltx_video_pipeline(535 ckpt_path=ltxv_model_path,536 precision=precision,537 text_encoder_model_name_or_path=text_encoder_model_name_or_path,538 sampler=sampler,539 device=kwargs.get("device", get_device()),540 enhance_prompt=enhance_prompt,541 prompt_enhancer_image_caption_model_name_or_path=prompt_enhancer_image_caption_model_name_or_path,542 prompt_enhancer_llm_model_name_or_path=prompt_enhancer_llm_model_name_or_path,543 )544 545 if pipeline_config.get("pipeline_type", None) == "multi-scale":546 if not spatial_upscaler_model_path:547 raise ValueError(548 "spatial upscaler model path is missing from pipeline config file and is required for multi-scale rendering"549 )550 latent_upsampler = create_latent_upsampler(551 spatial_upscaler_model_path, pipeline.device552 )553 pipeline = LTXMultiScalePipeline(pipeline, latent_upsampler=latent_upsampler)554 555 media_item = None556 if input_media_path:557 media_item = load_media_file(558 media_path=input_media_path,559 height=height,560 width=width,561 max_frames=num_frames_padded,562 padding=padding,563 )564 565 conditioning_items = (566 prepare_conditioning(567 conditioning_media_paths=conditioning_media_paths,568 conditioning_strengths=conditioning_strengths,569 conditioning_start_frames=conditioning_start_frames,570 height=height,571 width=width,572 num_frames=num_frames,573 padding=padding,574 pipeline=pipeline,575 )576 if conditioning_media_paths577 else None578 )579 580 stg_mode = pipeline_config.get("stg_mode", "attention_values")581 del pipeline_config["stg_mode"]582 if stg_mode.lower() == "stg_av" or stg_mode.lower() == "attention_values":583 skip_layer_strategy = SkipLayerStrategy.AttentionValues584 elif stg_mode.lower() == "stg_as" or stg_mode.lower() == "attention_skip":585 skip_layer_strategy = SkipLayerStrategy.AttentionSkip586 elif stg_mode.lower() == "stg_r" or stg_mode.lower() == "residual":587 skip_layer_strategy = SkipLayerStrategy.Residual588 elif stg_mode.lower() == "stg_t" or stg_mode.lower() == "transformer_block":589 skip_layer_strategy = SkipLayerStrategy.TransformerBlock590 else:591 raise ValueError(f"Invalid spatiotemporal guidance mode: {stg_mode}")592 593 # Prepare input for the pipeline594 sample = {595 "prompt": prompt,596 "prompt_attention_mask": None,597 "negative_prompt": negative_prompt,598 "negative_prompt_attention_mask": None,599 }600 601 device = device or get_device()602 generator = torch.Generator(device=device).manual_seed(seed)603 604 images = pipeline(605 **pipeline_config,606 skip_layer_strategy=skip_layer_strategy,607 generator=generator,608 output_type="pt",609 callback_on_step_end=None,610 height=height_padded,611 width=width_padded,612 num_frames=num_frames_padded,613 frame_rate=frame_rate,614 **sample,615 media_items=media_item,616 conditioning_items=conditioning_items,617 is_video=True,618 vae_per_channel_normalize=True,619 image_cond_noise_scale=image_cond_noise_scale,620 mixed_precision=(precision == "mixed_precision"),621 offload_to_cpu=offload_to_cpu,622 device=device,623 enhance_prompt=enhance_prompt,624 ).images625 626 # Crop the padded images to the desired resolution and number of frames627 (pad_left, pad_right, pad_top, pad_bottom) = padding628 pad_bottom = -pad_bottom629 pad_right = -pad_right630 if pad_bottom == 0:631 pad_bottom = images.shape[3]632 if pad_right == 0:633 pad_right = images.shape[4]634 images = images[:, :, :num_frames, pad_top:pad_bottom, pad_left:pad_right]635 636 for i in range(images.shape[0]):637 # Gathering from B, C, F, H, W to C, F, H, W and then permuting to F, H, W, C638 video_np = images[i].permute(1, 2, 3, 0).cpu().float().numpy()639 # Unnormalizing images to [0, 255] range640 video_np = (video_np * 255).astype(np.uint8)641 fps = frame_rate642 height, width = video_np.shape[1:3]643 # In case a single image is generated644 if video_np.shape[0] == 1:645 output_filename = get_unique_filename(646 f"image_output_{i}",647 ".png",648 prompt=prompt,649 seed=seed,650 resolution=(height, width, num_frames),651 dir=output_dir,652 )653 imageio.imwrite(output_filename, video_np[0])654 else:655 output_filename = get_unique_filename(656 f"video_output_{i}",657 ".mp4",658 prompt=prompt,659 seed=seed,660 resolution=(height, width, num_frames),661 dir=output_dir,662 )663 664 # Write video665 with imageio.get_writer(output_filename, fps=fps) as video:666 for frame in video_np:667 video.append_data(frame)668 669 logger.warning(f"Output saved to {output_filename}")670 671 672def prepare_conditioning(673 conditioning_media_paths: List[str],674 conditioning_strengths: List[float],675 conditioning_start_frames: List[int],676 height: int,677 width: int,678 num_frames: int,679 padding: tuple[int, int, int, int],680 pipeline: LTXVideoPipeline,681) -> Optional[List[ConditioningItem]]:682 """Prepare conditioning items based on input media paths and their parameters.683 684 Args:685 conditioning_media_paths: List of paths to conditioning media (images or videos)686 conditioning_strengths: List of conditioning strengths for each media item687 conditioning_start_frames: List of frame indices where each item should be applied688 height: Height of the output frames689 width: Width of the output frames690 num_frames: Number of frames in the output video691 padding: Padding to apply to the frames692 pipeline: LTXVideoPipeline object used for condition video trimming693 694 Returns:695 A list of ConditioningItem objects.696 """697 conditioning_items = []698 for path, strength, start_frame in zip(699 conditioning_media_paths, conditioning_strengths, conditioning_start_frames700 ):701 num_input_frames = orig_num_input_frames = get_media_num_frames(path)702 if hasattr(pipeline, "trim_conditioning_sequence") and callable(703 getattr(pipeline, "trim_conditioning_sequence")704 ):705 num_input_frames = pipeline.trim_conditioning_sequence(706 start_frame, orig_num_input_frames, num_frames707 )708 if num_input_frames < orig_num_input_frames:709 logger.warning(710 f"Trimming conditioning video {path} from {orig_num_input_frames} to {num_input_frames} frames."711 )712 713 media_tensor = load_media_file(714 media_path=path,715 height=height,716 width=width,717 max_frames=num_input_frames,718 padding=padding,719 just_crop=True,720 )721 conditioning_items.append(ConditioningItem(media_tensor, start_frame, strength))722 return conditioning_items723 724 725def get_media_num_frames(media_path: str) -> int:726 is_video = any(727 media_path.lower().endswith(ext) for ext in [".mp4", ".avi", ".mov", ".mkv"]728 )729 num_frames = 1730 if is_video:731 reader = imageio.get_reader(media_path)732 num_frames = reader.count_frames()733 reader.close()734 return num_frames735 736 737def load_media_file(738 media_path: str,739 height: int,740 width: int,741 max_frames: int,742 padding: tuple[int, int, int, int],743 just_crop: bool = False,744) -> torch.Tensor:745 is_video = any(746 media_path.lower().endswith(ext) for ext in [".mp4", ".avi", ".mov", ".mkv"]747 )748 if is_video:749 reader = imageio.get_reader(media_path)750 num_input_frames = min(reader.count_frames(), max_frames)751 752 # Read and preprocess the relevant frames from the video file.753 frames = []754 for i in range(num_input_frames):755 frame = Image.fromarray(reader.get_data(i))756 frame_tensor = load_image_to_tensor_with_resize_and_crop(757 frame, height, width, just_crop=just_crop758 )759 frame_tensor = torch.nn.functional.pad(frame_tensor, padding)760 frames.append(frame_tensor)761 reader.close()762 763 # Stack frames along the temporal dimension764 media_tensor = torch.cat(frames, dim=2)765 else: # Input image766 media_tensor = load_image_to_tensor_with_resize_and_crop(767 media_path, height, width, just_crop=just_crop768 )769 media_tensor = torch.nn.functional.pad(media_tensor, padding)770 return media_tensor771 772 773if __name__ == "__main__":774 main()