CoolFace
Modelpublic

unsloth/Kimi-K2.7-Code

sourceHugging Faceotherupdated 3mo agoView on Hugging Face
21likes124downloads
media_utils.py369 linesDownload Raw Back to root
1import base642import io3import math4import os5from datetime import datetime, timezone6from typing import List, Literal, Optional, TypedDict7 8import numpy as np9from PIL import Image10from pydantic import BaseModel, Field11 12try:13    from mecord import VideoReader14except ImportError:15    VideoReader = None16 17 18class VideoSpec(BaseModel):19    media_type: str = Literal['video']20    height: int = Field(..., gt=0, description="video frame height")21    width: int = Field(..., gt=0, description="video frame width")22    num_frames: int = Field(..., gt=0, description="num frames")23    fps: float = Field(..., gt=0, description="average fps")24 25    # optional, help to accelerate video reading26    key_indices: list[int] = Field(None, description="key indices")27    frame_time_info: dict = Field(None, description="frame time info")28 29 30class ImageInput(TypedDict):31    type: Literal['image']32    image: Image.Image33 34 35class VideoChunkInput(TypedDict):36    type: Literal['video_chunk']37    video_chunk: List[Image.Image]38    prompt: Optional[str] = None39 40 41MediaInput = ImageInput | VideoChunkInput42 43 44def get_video_meta(video_src: bytes | str | os.PathLike,45                   accurate: bool = True) -> dict:46    """Get the dimensions of a video."""47    if isinstance(video_src, os.PathLike):48        video_src = str(video_src)49    # if b64 string, decode to bytes50    if isinstance(video_src,51                  str) and video_src.startswith('data:video/mp4;base64,'):52        video_src = base64.b64decode(video_src.split(',')[1])53    video = VideoReader(video_src, auto_init=accurate, num_threads=1)54    assert video.num_frames > 0, "Invalid video format."55    assert video.original_width > 0 and video.original_height > 0, (56        "Invalid video format.")57    assert video.avg_fps > 0, "Invalid video format."58    return VideoSpec(media_type='video',59                     height=video.original_height,60                     width=video.original_width,61                     num_frames=video.num_frames,62                     fps=video.avg_fps,63                     key_indices=video.key_indices,64                     frame_time_info=video.frame_time_info)65 66 67def timestamp_as_str(timestamp: float,68                     timestamp_mode: str = "hh:mm:ss.fff") -> str:69    """Convert a timestamp to a string in the format of HH:MM:SS.mmm."""70    if timestamp_mode == "hh:mm:ss.fff":71        return (datetime.fromtimestamp(timestamp,72                                       tz=timezone.utc).strftime("%H:%M:%S") +73                f".{int((timestamp % 1) * 1000):03d}")74    elif timestamp_mode == "mm:ss.fff":75        return (datetime.fromtimestamp(timestamp,76                                       tz=timezone.utc).strftime("%M:%S") +77                f".{int((timestamp % 1) * 1000):03d}")78    elif timestamp_mode == "mm:ss":79        return datetime.fromtimestamp(timestamp,80                                      tz=timezone.utc).strftime("%M:%S")81    else:82        raise ValueError(f"Invalid timestamp mode: {timestamp_mode}")83 84 85def navit_resize_image(86    width: int,87    height: int,88    patch_size: int,89    merge_kernel_size: int,90    in_patch_limit: int,91    patch_limit_on_one_side: int,92    fixed_output_tokens: int | None,93):94    # Apply the patch limits.95    s1 = math.sqrt(96        in_patch_limit /97        (max(1.0, width // patch_size) * max(1.0, height // patch_size)))98    s2 = patch_limit_on_one_side * patch_size / width99    s3 = patch_limit_on_one_side * patch_size / height100    scale = min(1.0, s1, s2, s3)101    new_w, new_h = max(1, int(width * scale)), max(1, int(height * scale))102    new_w = min(new_w, patch_limit_on_one_side * patch_size)103    new_h = min(new_h, patch_limit_on_one_side * patch_size)104 105    # Calculate the padding to make the height and width divisible by the merge kernel size and patch size.106    factor = merge_kernel_size * patch_size107 108    pad_height = (factor - new_h % factor) % factor109    pad_width = (factor - new_w % factor) % factor110 111    if fixed_output_tokens is not None:112        num_tokens = fixed_output_tokens113    else:114        # Calculate new dimensions after padding and patching115        token_height = (new_h + pad_height) // factor116        token_width = (new_w + pad_width) // factor117 118        assert token_height * merge_kernel_size <= patch_limit_on_one_side, (119            f"token_height {token_height} * merge_kernel_size {merge_kernel_size} > patch_limit_on_one_side {patch_limit_on_one_side}"120        )121        assert token_width * merge_kernel_size <= patch_limit_on_one_side, (122            f"token_width {token_width} * merge_kernel_size {merge_kernel_size} > patch_limit_on_one_side {patch_limit_on_one_side}"123        )124 125        num_tokens = token_height * token_width126    return {127        "num_tokens": num_tokens,128        "new_width": new_w,129        "new_height": new_h,130        "pad_width": pad_width,131        "pad_height": pad_height,132        "sampled_nframes": 1,133    }134 135 136def navit_resize_video(137    width: int,138    height: int,139    nframes: int,140    avg_fps: float,141    sample_fps: float,142    patch_size: int,143    merge_kernel_size: int,144    in_patch_limit_each_frame: int,145    patch_limit_on_one_side: int,146    in_patch_limit_total: int | None,147    max_num_frames_each_video: int | None,148    fixed_output_tokens_each_frame: int | None,149):150    sample_fps = min(sample_fps, avg_fps)151    # Calculate the number of frames to sample based on target FPS152    sampled_nframes = max(round(nframes * sample_fps / avg_fps), 1)153    if max_num_frames_each_video is not None:154        sampled_nframes = min(sampled_nframes, max_num_frames_each_video)155 156    if in_patch_limit_total is not None:157        in_patch_limit_each_frame = min(158            round(in_patch_limit_total / sampled_nframes),159            in_patch_limit_each_frame)160 161    ret = navit_resize_image(162        width,163        height,164        patch_size,165        merge_kernel_size,166        in_patch_limit_each_frame,167        patch_limit_on_one_side,168        fixed_output_tokens_each_frame,169    )170    ret["sampled_nframes"] = sampled_nframes171    return ret172 173 174def real_sample_fps_and_max_num_frames(175    type_name: Literal["video", "video_chunk"],176    sample_fps: float,177    max_num_frames_each_video: int | None,178) -> tuple[int, int | None]:179    if type_name == "video":180        return sample_fps, max_num_frames_each_video181    elif type_name == "video_chunk":182        max_num_frames_each_video = None183        sample_fps = math.inf184        return sample_fps, max_num_frames_each_video185    else:186        return math.inf, None187 188 189def _to_pil(data: str | bytes):190    if isinstance(data, Image.Image):191 192        return data.convert("RGB")193    elif isinstance(data, str):194        if data.startswith("data:"):195            raw_base64 = data.split(",")[1]196            return Image.open(io.BytesIO(197                base64.b64decode(raw_base64))).convert("RGB")198        else:199            return Image.open(data).convert("RGB")200    elif isinstance(data, bytes):201        return Image.open(io.BytesIO(data)).convert("RGB")202    else:203        raise ValueError(f"Unsupported data type: {type(data)}")204 205 206def ensure_media_type(media: MediaInput) -> MediaInput:207    if media['type'] == 'image':208        media['image'] = _to_pil(media['image'])209        return media210    elif media['type'] == 'video_chunk':211        media['video_chunk'] = [212            _to_pil(frame) for frame in media['video_chunk']213        ]214        return media215    else:216        raise ValueError(f"Unsupported media type: {media['type']}")217 218 219def image_to_np(220    image: Image.Image,221    resize_to: tuple[int, int] | None = None,222    mode: str = "resize",223    raise_error_for_ill_resize: bool = True,224) -> np.ndarray:225    """Convert an image to a numpy array.226 227    Args:228        content: The image to convert.229        resize_to: The size to resize the image to.230        mode: The mode to resize the image to.231        raise_error_for_ill_resize: Whether to raise an error for ill-sized resize.232 233    Returns:234        A numpy array.235    """236    assert isinstance(image, Image.Image), "image must be a PIL Image"237    if resize_to is not None:238        if mode == "resize":239            image = image.resize(resize_to, resample=Image.Resampling.BICUBIC)240 241        elif mode == "rescale_and_pad_to_center":242            scale = min(resize_to[0] / image.width,243                        resize_to[1] / image.height, 1.0)244            new_width = round(image.width * scale)245            new_height = round(image.height * scale)246            if new_width == 0 or new_height == 0:247                if raise_error_for_ill_resize:248                    raise ValueError(249                        f"Invalid resize to: {resize_to}, from image size: {image.size}"250                    )251                else:252                    return np.zeros((resize_to[1], resize_to[0], 3),253                                    dtype=np.uint8)254 255            image = image.resize((new_width, new_height),256                                 resample=Image.Resampling.BICUBIC)257            padding_left = (resize_to[0] - new_width) // 2258            padding_right = resize_to[0] - new_width - padding_left259            padding_top = (resize_to[1] - new_height) // 2260            padding_bottom = resize_to[1] - new_height - padding_top261            image = np.asarray(image)262            image = np.pad(263                image,264                ((padding_top, padding_bottom), (padding_left, padding_right),265                 (0, 0)),266                mode="constant",267                constant_values=0,268            )269            assert image.shape == (resize_to[1], resize_to[0], 3)270 271        elif mode == "rescale_and_pad_to_rightbottom":272            scale = min(resize_to[0] / image.width,273                        resize_to[1] / image.height, 1.0)274            new_width = round(image.width * scale)275            new_height = round(image.height * scale)276            if new_width == 0 or new_height == 0:277                if raise_error_for_ill_resize:278                    raise ValueError(279                        f"Invalid resize to: {resize_to}, from image size: {image.size}"280                    )281                else:282                    return np.zeros((resize_to[1], resize_to[0], 3),283                                    dtype=np.uint8)284 285            image = image.resize((new_width, new_height),286                                 resample=Image.Resampling.BICUBIC)287            padding_right = resize_to[0] - new_width288            padding_bottom = resize_to[1] - new_height289            image = np.asarray(image)290            image = np.pad(291                image,292                ((0, padding_bottom), (0, padding_right), (0, 0)),293                mode="constant",294                constant_values=0,295            )296            assert image.shape == (resize_to[1], resize_to[0], 3)297 298        else:299            raise ValueError(f"Invalid mode: {mode}")300 301    if isinstance(image, Image.Image):302        return np.asarray(image)303    else:304        return image305 306 307def navit_patchify(pixel_values: np.ndarray,308                   patch_size: int) -> dict[str, np.ndarray]:309    """Reshape the pixel values to a navit shape.310 311    Args:312        pixel_values: np.ndarray, shape (t, h, w, c)313        patch_size: int314 315    Returns:316        dict[str, np.ndarray]317        - patches: np.ndarray, shape (t * h//patch_size * w//patch_size, c, patch_size, patch_size)318        - grid_thw: np.ndarray, (t, h//patch_size, w//patch_size)319    """320    T, H, W, C = pixel_values.shape321    assert C == 3, "pixel_values must have 3 channels"322 323    patches = pixel_values.reshape(T, H // patch_size, patch_size,324                                   W // patch_size, patch_size, C)325    # (T, H//patch_size, W//patch_size, C, patch_size, patch_size)326    patches = patches.transpose(0, 1, 3, 5, 2, 4)327    patches = patches.reshape(-1, C, patch_size, patch_size)328    grid_thw = np.array([T, H // patch_size, W // patch_size])329    return {"pixel_values": patches, "grid_thw": grid_thw}330 331 332def normalize(x: np.ndarray,333              mean,334              std_inv,335              pixels_dtype: np.dtype = np.float32) -> np.ndarray:336    """Normalize the image.337 338    Args:339        x: The image to normalize. The shape is (..., 3). The dtype is uint8. The range is [0, 255].340        mean: The mean of the image.341        std_inv: The inverse of the std of the image.342        pixels_dtype: The dtype of the image.343    Returns:344        The normalized image. The shape is (..., 3). The dtype is determined by the pixels_dtype.345    """346    x = (x / 255.0).astype(pixels_dtype)347    x -= mean348    x *= std_inv349    return x350 351 352def _to_tensor(data, **kwargs):353    import torch354 355    if isinstance(data, np.ndarray):356        return torch.from_numpy(data).to(**kwargs)357    elif isinstance(data, torch.Tensor):358        return data.to(**kwargs)359    elif isinstance(data, list):360        return [_to_tensor(item, **kwargs) for item in data]361    elif isinstance(data, tuple):362        return tuple(_to_tensor(item, **kwargs) for item in data)363    elif isinstance(data, dict):364        return {k: _to_tensor(v, **kwargs) for k, v in data.items()}365    elif data is None:366        return None367    else:368        raise ValueError(f"Unsupported data type: {type(data)}")369