multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
ee69ab7 verified
Raw History Blame Contribute Delete
4.22 kB
from __future__ import annotations
from collections.abc import Iterator
import torch
from .padding import target_frame_count
class TemporalVideoStream:
"""Expand a source timeline on demand and release consumed LQ frames.
Only EOF introduces temporal padding. Frame indices refer to the expanded
model timeline, including its unique first frame.
"""
def __init__(self, chunks: Iterator[torch.Tensor], compression: int, model_compression: int = 4):
if compression not in (1, 2, 4) or model_compression % compression:
raise ValueError("invalid temporal compression")
self.chunks = iter(chunks)
self.compression = compression
self.repeat_factor = model_compression // compression
self.source_frames = 0
self.effective_frames = 0
self.expanded_frames = 0
self.eof = False
self.buffer = None
self.offset = 0
self.last_frame = None
self.max_buffer_frames = 0
def _append(self, value: torch.Tensor) -> None:
if not value.shape[2]:
return
self.buffer = value.clone() if self.buffer is None else torch.cat((self.buffer, value), dim=2)
self.expanded_frames += value.shape[2]
self.max_buffer_frames = max(self.max_buffer_frames, self.buffer.shape[2])
def ensure(self, stop: int) -> bool:
while self.expanded_frames < stop and not self.eof:
try:
value = next(self.chunks)
except StopIteration:
self.eof = True
if not self.source_frames:
raise ValueError("No frames decoded from input video")
self.effective_frames = target_frame_count(self.source_frames, self.compression)
missing = (self.effective_frames - self.source_frames) * self.repeat_factor
self._append(self.last_frame.repeat(1, 1, missing, 1, 1))
break
if value.ndim != 5 or value.shape[:2] != (1, 3) or not value.shape[2]:
raise ValueError("Input chunks must be nonempty [1,3,F,H,W] tensors")
if self.last_frame is not None and value.shape[-2:] != self.last_frame.shape[-2:]:
raise ValueError("Input chunk dimensions changed")
first = self.source_frames == 0
self.source_frames += value.shape[2]
self.last_frame = value[:, :, -1:].clone()
if first:
expanded = torch.cat((value[:, :, :1], value[:, :, 1:].repeat_interleave(self.repeat_factor, dim=2)), dim=2)
else:
expanded = value.repeat_interleave(self.repeat_factor, dim=2)
self._append(expanded)
return self.expanded_frames >= stop
def window(self, start: int, stop: int) -> torch.Tensor:
self.ensure(stop)
if start < self.offset or stop <= start:
raise ValueError("Requested frames have already been released or window is empty")
value = self.buffer[:, :, start - self.offset:stop - self.offset]
if not value.shape[2]:
value = self.last_frame[:, :, :0]
missing = stop - start - value.shape[2]
if missing:
value = torch.cat((value, self.last_frame.repeat(1, 1, missing, 1, 1)), dim=2)
return value.clone()
def release_before(self, frame: int) -> None:
frame = min(frame, self.expanded_frames)
if frame > self.offset:
self.buffer = self.buffer[:, :, frame - self.offset:].clone()
self.offset = frame
def projection_chunks(self, chunk_latents: int, model_compression: int):
index = 0
stride = chunk_latents * model_compression
while self.ensure(1 + index * stride):
start = 0 if index == 0 else 1 + index * stride
stop = 1 + (index + 1) * stride
value = self.window(start, stop)
last = self.eof and self.expanded_frames < stop
yield index, value, start, last
index += 1
def close(self) -> None:
close = getattr(self.chunks, "close", None)
if close is not None:
close()
self.buffer = None
self.last_frame = None