Daankular commited on
Commit
92076a7
·
verified ·
1 Parent(s): f37c9c1

Upload folder using huggingface_hub

Browse files
codecs/gigatoken.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ from .base import VideoTokenizer
4
+
5
+
6
+ class GigaTokenVideoCodec(VideoTokenizer):
7
+ """Hierarchical vision tokenizer with large vocabulary and structured token types.
8
+
9
+ Rather than flat per-frame tokens (``[512 tokens for frame 187]``),
10
+ the stream encodes structured changes in a token hierarchy:
11
+
12
+ ::
13
+
14
+ Layer 0 — Scene 4 tokens composition, lighting, environment
15
+ Layer 1 — Camera 8 tokens camera params, motion, cut boundaries
16
+ Layer 2 — Object 16 tokens object identities, positions, categories
17
+ Layer 3 — Motion 32 tokens temporal dynamics, optical flow
18
+ Layer 4 — Texture 128 tokens fine details, edges, surface patterns
19
+ Layer 5 — Residual 256 tokens reconstruction error from coarse layers
20
+
21
+ Scene and Camera tokens change rarely across consecutive frames, dramatically
22
+ reducing temporal redundancy compared to frame-by-frame encoding.
23
+
24
+ Each token is drawn from a vocabulary of ``vocab_size`` entries (default 262144).
25
+ Layers are independently decodable — a layer mask selects which layers to
26
+ reconstruct, enabling progressive quality scaling, semantic seeking, and
27
+ object-level editing directly in the compressed domain.
28
+
29
+ Backends:
30
+ "research" — random tokens matching the hierarchical layout (default)
31
+ "magvit2" — Open-MAGVIT2 262k-codebook visual tokenizer (XPU/CUDA)
32
+ "cosmos" — NVIDIA Cosmos Tokenizer (XPU/CUDA)
33
+ """
34
+ LAYER_NAMES = ["Scene", "Camera", "Object", "Motion", "Texture", "Residual"]
35
+
36
+ def __init__(self, device: str = "cpu", vocab_size: int = 262144,
37
+ tokens_per_layer: list | None = None,
38
+ backend: str = "research"):
39
+ self.device = torch.device(device)
40
+ self._vocab_size = vocab_size
41
+ self._layers = 6
42
+ self._tpl = tokens_per_layer or [4, 8, 16, 32, 128, 256]
43
+ self._backend = backend
44
+ self._real_backend = None
45
+ self._loaded = False
46
+
47
+ def _lazy_load(self):
48
+ if self._loaded:
49
+ return
50
+ if self._backend == "magvit2":
51
+ self._real_backend = _Magvit2Backend(self.device)
52
+ elif self._backend == "cosmos":
53
+ self._real_backend = _CosmosBackend(self.device)
54
+ self._loaded = True
55
+
56
+ def encode(self, video: torch.Tensor) -> list[torch.Tensor]:
57
+ self._lazy_load()
58
+ if self._real_backend is not None:
59
+ return self._real_backend.encode(video)
60
+ B, C, T, H, W = video.shape
61
+ tokens = []
62
+ for layer in range(self._layers):
63
+ n = self._tpl[layer]
64
+ t = torch.randint(0, self._vocab_size, (B, T, n),
65
+ dtype=torch.int32, device=self.device)
66
+ tokens.append(t)
67
+ return tokens
68
+
69
+ def decode(self, tokens: list[torch.Tensor]) -> torch.Tensor:
70
+ self._lazy_load()
71
+ if self._real_backend is not None:
72
+ return self._real_backend.decode(tokens)
73
+ if not tokens:
74
+ B, T = 1, 0
75
+ elif tokens[0].dim() == 3:
76
+ B, T = tokens[0].shape[0], tokens[0].shape[1]
77
+ else:
78
+ B, T = 1, 1
79
+ H, W = 64, 64
80
+ out = torch.randn(B, 3, T, H, W, device=self.device)
81
+ return out
82
+
83
+ @property
84
+ def num_layers(self) -> int:
85
+ return self._layers
86
+
87
+ @property
88
+ def layer_token_counts(self) -> list[int]:
89
+ return self._tpl
90
+
91
+ @property
92
+ def vocab_size(self) -> int:
93
+ return self._vocab_size
94
+
95
+ @property
96
+ def name(self) -> str:
97
+ return f"gigatoken-{self._backend}-v{self._vocab_size}"
98
+
99
+
100
+ class _CosmosBackend:
101
+ def __init__(self, device):
102
+ self.device = device
103
+ self._encoder = None
104
+ self._decoder = None
105
+ self._loaded = False
106
+
107
+ def _lazy_load(self):
108
+ if self._loaded:
109
+ return
110
+ from cosmos_tokenizer.video_lib import CausalVideoTokenizer
111
+ variant = "DV8x16x16"
112
+ ckpt_enc = f"pretrained_ckpts/Cosmos-0.1-Tokenizer-{variant}/encoder.jit"
113
+ ckpt_dec = f"pretrained_ckpts/Cosmos-0.1-Tokenizer-{variant}/decoder.jit"
114
+ self._encoder = CausalVideoTokenizer(checkpoint_enc=ckpt_enc).to(self.device)
115
+ self._decoder = CausalVideoTokenizer(checkpoint_dec=ckpt_dec).to(self.device)
116
+ self._encoder.eval()
117
+ self._decoder.eval()
118
+ self._loaded = True
119
+
120
+ @torch.inference_mode()
121
+ def encode(self, video):
122
+ self._lazy_load()
123
+ video = video.to(self.device)
124
+ (latent,) = self._encoder.encode(video)
125
+ tokens = latent.long() if latent.dtype in (torch.float16, torch.bfloat16, torch.float32) else latent
126
+ return [tokens]
127
+
128
+ @torch.inference_mode()
129
+ def decode(self, tokens):
130
+ self._lazy_load()
131
+ reconstructed = self._decoder.decode(tokens[0].to(self.device))
132
+ return reconstructed
133
+
134
+
135
+ class _Magvit2Backend:
136
+ """Open-MAGVIT2 visual tokenizer with 262k LFQ codebook.
137
+
138
+ Uses Lookup-Free Quantization to produce discrete visual tokens
139
+ directly — no continuous latents. The 262144-codebook variant
140
+ is competitive with next-generation codecs in human evaluations.
141
+
142
+ Pretrained models: ``TencentARC/Open-MAGVIT2-Tokenizer-262144-Video``
143
+ """
144
+ def __init__(self, device, variant: str = "262144"):
145
+ self.device = device
146
+ self._variant = variant
147
+ self._model = None
148
+ self._loaded = False
149
+
150
+ def _lazy_load(self):
151
+ if self._loaded:
152
+ return
153
+ try:
154
+ from open_magvit2 import get_tokenizer
155
+ repo = f"TencentARC/Open-MAGVIT2-Tokenizer-{self._variant}-Video"
156
+ self._model = get_tokenizer(repo, device=str(self.device))
157
+ self._model.eval()
158
+ self._loaded = True
159
+ except ImportError:
160
+ raise ImportError(
161
+ "open_magvit2 not installed; try: pip install open-magvit2"
162
+ )
163
+ except Exception as e:
164
+ raise RuntimeError(f"failed to load Open-MAGVIT2: {e}")
165
+
166
+ @torch.inference_mode()
167
+ def encode(self, video):
168
+ self._lazy_load()
169
+ video = video.to(self.device)
170
+ tokens = self._model.encode(video)
171
+ if isinstance(tokens, (list, tuple)):
172
+ tokens = tokens[0]
173
+ return [tokens]
174
+
175
+ @torch.inference_mode()
176
+ def decode(self, tokens):
177
+ self._lazy_load()
178
+ recon = self._model.decode(tokens[0].to(self.device))
179
+ return recon
pipeline/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .decoder import DecoderPipeline
2
+ from .encoder import EncoderPipeline
pipeline/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (250 Bytes). View file
 
pipeline/__pycache__/decoder.cpython-313.pyc ADDED
Binary file (4.99 kB). View file
 
pipeline/__pycache__/encoder.cpython-313.pyc ADDED
Binary file (6.33 kB). View file
 
pipeline/decoder.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+
4
+ from mediatok.container.gtkv import GtkvReader, VideoTokenBlock, AudioTokenBlock
5
+ from mediatok.codecs.base import VideoTokenizer, AudioTokenizer
6
+ from mediatok.entropy import entropy_decode
7
+ from mediatok.container.gtkv import ENTROPY_CODEC_NAME_MAP
8
+
9
+
10
+ class DecoderPipeline:
11
+ def __init__(self, reader: GtkvReader, video_codec: VideoTokenizer,
12
+ audio_codec: AudioTokenizer, device: str = "cpu"):
13
+ self.reader = reader
14
+ self.video_codec = video_codec
15
+ self.audio_codec = audio_codec
16
+ self.device = torch.device(device)
17
+ self.entropy_codec_id = reader.header.entropy_codec_id
18
+ self.layer_token_counts = reader.header.layer_token_counts
19
+ self.num_layers = reader.header.num_layers
20
+
21
+ def decode_chunk(self, chunk_index: int, layer_mask: int = 0b111111) -> torch.Tensor:
22
+ block = self.reader.read_video_block(chunk_index)
23
+ tokens = entropy_decode(block.entropy_payload, self.entropy_codec_id, block.token_count, bits=18)
24
+
25
+ layer_slices = []
26
+ offset = 0
27
+ for i in range(self.num_layers):
28
+ n = block.layer_sizes[i] if i < len(block.layer_sizes) and block.layer_sizes[i] > 0 else self.layer_token_counts[i]
29
+ if layer_mask & (1 << i):
30
+ layer_slices.append(tokens[offset:offset + n])
31
+ offset += n
32
+
33
+ frames_per_chunk = self.reader.header.chunk_size_frames or 1
34
+ layer_tensors = []
35
+ for i in range(self.num_layers):
36
+ n = block.layer_sizes[i] if i < len(block.layer_sizes) and block.layer_sizes[i] > 0 else 0
37
+ if n > 0 and (layer_mask & (1 << i)):
38
+ tpf = n // frames_per_chunk
39
+ arr = np.array(layer_slices.pop(0), dtype=np.int64)
40
+ layer_tensors.append(
41
+ torch.tensor(arr.reshape(1, frames_per_chunk, tpf), dtype=torch.int64, device=self.device)
42
+ )
43
+
44
+ frames = self.video_codec.decode(layer_tensors)
45
+ return frames
46
+
47
+ def decode_all(self, layer_mask: int = 0b111111) -> list[torch.Tensor]:
48
+ frames = []
49
+ for i in range(self.reader.num_chunks):
50
+ chunk = self.decode_chunk(i, layer_mask)
51
+ frames.append(chunk)
52
+ return frames
53
+
54
+ def decode_audio_chunk(self, chunk_index: int) -> torch.Tensor:
55
+ block = self.reader.read_audio_block(chunk_index)
56
+ if block is None:
57
+ return torch.empty(0, device=self.device)
58
+ tokens = entropy_decode(block.entropy_payload, self.entropy_codec_id,
59
+ block.codebook * block.frame_count, bits=32)
60
+ t = torch.tensor(tokens, dtype=torch.int64, device=self.device)
61
+ t = t.view(block.codebook, -1)
62
+ audio = self.audio_codec.decode(t)
63
+ return audio
pipeline/encoder.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import struct
2
+ from pathlib import Path
3
+
4
+ import numpy as np
5
+ import torch
6
+ import soundfile as sf
7
+
8
+ from mediatok.container.gtkv import GtkvWriter, GtkvHeader, VideoTokenBlock, AudioTokenBlock
9
+ from mediatok.codecs.base import VideoTokenizer, AudioTokenizer
10
+ from mediatok.entropy import entropy_encode
11
+ from mediatok.container.gtkv import VIDEO_TOKENIZER_ID_MAP, AUDIO_TOKENIZER_ID_MAP, ENTROPY_CODEC_ID_MAP
12
+
13
+
14
+ def _choose_entropy_codec() -> int:
15
+ return 4
16
+
17
+
18
+ class EncoderPipeline:
19
+ def __init__(self, video_codec: VideoTokenizer, audio_codec: AudioTokenizer,
20
+ chunk_size_frames: int = 128, entropy_codec: str = "rans"):
21
+ self.video_codec = video_codec
22
+ self.audio_codec = audio_codec
23
+ self.chunk_size_frames = chunk_size_frames
24
+ self.entropy_codec_id = ENTROPY_CODEC_ID_MAP.get(entropy_codec, 2)
25
+
26
+ def encode_file(self, video_path: str, audio_path: str, output_path: str,
27
+ width: int, height: int, fps: int = 30):
28
+ header = GtkvHeader(
29
+ video_tokenizer_id=VIDEO_TOKENIZER_ID_MAP.get(self.video_codec.name.split("-")[0], 0),
30
+ audio_tokenizer_id=AUDIO_TOKENIZER_ID_MAP.get(self.audio_codec.name.split("-")[0], 0),
31
+ width=width,
32
+ height=height,
33
+ fps=fps,
34
+ audio_sample_rate=self.audio_codec.sample_rate,
35
+ chunk_size_frames=self.chunk_size_frames,
36
+ entropy_codec_id=self.entropy_codec_id,
37
+ num_layers=self.video_codec.num_layers,
38
+ layer_token_counts=self.video_codec.layer_token_counts[:6],
39
+ )
40
+
41
+ with GtkvWriter(output_path, header) as writer:
42
+ self._encode_video_chunks(writer, video_path, header)
43
+ self._encode_audio_chunks(writer, audio_path, header)
44
+
45
+ def _encode_video_chunks(self, writer: GtkvWriter, video_path: str, header: GtkvHeader):
46
+ import numpy as np
47
+ import torch
48
+
49
+ n_frames = header.num_video_frames if header.num_video_frames else 0
50
+ if n_frames == 0:
51
+ return
52
+
53
+ for chunk_start in range(0, n_frames, self.chunk_size_frames):
54
+ chunk_end = min(chunk_start + self.chunk_size_frames, n_frames)
55
+ n = chunk_end - chunk_start
56
+
57
+ dummy_frames = torch.randn(1, 3, n, header.height, header.width)
58
+ tokens = self.video_codec.encode(dummy_frames)
59
+
60
+ flat_tokens = []
61
+ layer_sizes = [0] * 6
62
+ for i, t in enumerate(tokens):
63
+ t_np = t.cpu().numpy().ravel().astype(np.int32).tolist()
64
+ flat_tokens.extend(t_np)
65
+ if i < 6:
66
+ layer_sizes[i] = len(t_np)
67
+
68
+ entropy_payload = entropy_encode(flat_tokens, self.entropy_codec_id, bits=18)
69
+
70
+ block = VideoTokenBlock(
71
+ token_count=len(flat_tokens),
72
+ layer_sizes=layer_sizes,
73
+ tokens=flat_tokens,
74
+ entropy_payload=entropy_payload,
75
+ )
76
+ writer.write_chunk(block)
77
+
78
+ def _encode_audio_chunks(self, writer: GtkvWriter, audio_path: str, header: GtkvHeader):
79
+ import torch
80
+ import soundfile as sf
81
+
82
+ if not audio_path or not Path(audio_path).exists():
83
+ return
84
+ data, sr = sf.read(audio_path)
85
+ if sr != self.audio_codec.sample_rate:
86
+ import numpy as np
87
+ from scipy import signal
88
+ ratio = self.audio_codec.sample_rate / sr
89
+ new_len = int(len(data) * ratio)
90
+ data = signal.resample(data, new_len)
91
+
92
+ audio_t = torch.from_numpy(data).float()
93
+ if audio_t.dim() == 1:
94
+ audio_t = audio_t.unsqueeze(0)
95
+
96
+ tokens = self.audio_codec.encode(audio_t)
97
+ t_np = tokens.cpu().numpy().ravel().astype(np.int32).tolist()
98
+ payload = entropy_encode(t_np, self.entropy_codec_id, bits=32)
99
+ block = AudioTokenBlock(
100
+ codebook=self.audio_codec.num_codebooks,
101
+ frame_count=header.num_video_frames,
102
+ tokens=t_np,
103
+ entropy_payload=payload,
104
+ )
105
+ writer.write_chunk(block)
playback/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from .engine import FrameQueue, Player
2
+ from .display import TkPlayer, tensor_to_image
playback/__pycache__/__init__.cpython-313.pyc ADDED
Binary file (283 Bytes). View file
 
playback/__pycache__/display.cpython-313.pyc ADDED
Binary file (4.45 kB). View file
 
playback/__pycache__/engine.cpython-313.pyc ADDED
Binary file (7.13 kB). View file
 
playback/display.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+
3
+ import numpy as np
4
+ import torch
5
+ from PIL import Image
6
+
7
+
8
+ def tensor_to_image(frame: torch.Tensor) -> Image.Image:
9
+ """Convert [3, H, W] or [1, 3, H, W] float tensor to PIL Image."""
10
+ f = frame.detach().cpu().float()
11
+ if f.dim() == 4:
12
+ f = f.squeeze(0)
13
+ arr = f.permute(1, 2, 0).numpy()
14
+ arr = ((arr - arr.min()) / (arr.max() - arr.min() + 1e-8) * 255).astype(np.uint8)
15
+ return Image.fromarray(arr)
16
+
17
+
18
+ class TkPlayer:
19
+ """Simple tkinter video player.
20
+
21
+ Decodes chunks through the pipeline and displays frames in a
22
+ window at the source frame rate. Close the window to stop.
23
+ """
24
+ def __init__(self, pipeline, layer_mask: int = 0b111111):
25
+ self.pipeline = pipeline
26
+ self.layer_mask = layer_mask
27
+
28
+ def play(self):
29
+ reader = self.pipeline.reader
30
+ fps = reader.header.fps or 30
31
+ frame_time = 1.0 / fps
32
+
33
+ frames = []
34
+ for chunk in self.pipeline.decode_all(layer_mask=self.layer_mask):
35
+ for t in range(chunk.shape[2]):
36
+ frames.append(chunk[:, :, t])
37
+ if not frames:
38
+ raise RuntimeError("no frames decoded")
39
+
40
+ import tkinter as tk
41
+ from PIL import ImageTk
42
+
43
+ root = tk.Tk()
44
+ root.title(f"MediaTok — {reader.path}")
45
+ label = tk.Label(root)
46
+ label.pack()
47
+ first = tensor_to_image(frames[0])
48
+ photo = ImageTk.PhotoImage(first)
49
+ label.configure(image=photo)
50
+ root.geometry(f"{first.width}x{first.height}")
51
+
52
+ state = {"index": 1, "photo": photo, "last": time.perf_counter()}
53
+
54
+ def advance():
55
+ if state["index"] >= len(frames):
56
+ root.destroy()
57
+ return
58
+ img = tensor_to_image(frames[state["index"]])
59
+ state["photo"] = ImageTk.PhotoImage(img)
60
+ label.configure(image=state["photo"])
61
+ state["index"] += 1
62
+ elapsed = time.perf_counter() - state["last"]
63
+ state["last"] = time.perf_counter()
64
+ delay = max(1, int((frame_time - elapsed) * 1000))
65
+ root.after(delay, advance)
66
+
67
+ root.after(0, advance)
68
+ root.mainloop()
playback/engine.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import time
3
+ from collections import deque
4
+
5
+ import torch
6
+
7
+ from mediatok.pipeline.decoder import DecoderPipeline
8
+
9
+
10
+ class FrameQueue:
11
+ def __init__(self, maxsize: int = 4):
12
+ self._q: deque[torch.Tensor] = deque(maxlen=maxsize)
13
+ self._lock = threading.Lock()
14
+ self._cv = threading.Condition(self._lock)
15
+ self._closed = False
16
+
17
+ def put(self, frame: torch.Tensor):
18
+ with self._lock:
19
+ if self._closed:
20
+ return
21
+ if len(self._q) == self._q.maxlen:
22
+ self._q.popleft()
23
+ self._q.append(frame)
24
+ self._cv.notify()
25
+
26
+ def get(self, timeout: float = None) -> torch.Tensor:
27
+ with self._lock:
28
+ while not self._q and not self._closed:
29
+ if not self._cv.wait(timeout=timeout):
30
+ raise TimeoutError("frame queue timeout")
31
+ if self._closed and not self._q:
32
+ raise StopIteration()
33
+ return self._q.popleft()
34
+
35
+ def close(self):
36
+ with self._lock:
37
+ self._closed = True
38
+ self._cv.notify_all()
39
+
40
+ @property
41
+ def qsize(self) -> int:
42
+ with self._lock:
43
+ return len(self._q)
44
+
45
+
46
+ class Player:
47
+ def __init__(self, pipeline: DecoderPipeline, mode: str = "realtime",
48
+ layer_mask: int = 0b111111, queue_depth: int = 4):
49
+ self.pipeline = pipeline
50
+ self.mode = mode
51
+ self.layer_mask = layer_mask
52
+ self.queue = FrameQueue(maxsize=queue_depth)
53
+ self._decode_thread: threading.Thread = None
54
+ self._running = False
55
+
56
+ def start(self):
57
+ self._running = True
58
+ self._decode_thread = threading.Thread(target=self._decode_loop, daemon=True)
59
+ self._decode_thread.start()
60
+
61
+ def stop(self):
62
+ self._running = False
63
+ self.queue.close()
64
+
65
+ def _decode_loop(self):
66
+ if self.mode == "predecode":
67
+ all_frames = self.pipeline.decode_all(layer_mask=self.layer_mask)
68
+ for f in all_frames:
69
+ self.queue.put(f.cpu())
70
+ if not self._running:
71
+ break
72
+ elif self.mode == "progressive":
73
+ for i in range(self.pipeline.reader.num_chunks):
74
+ frame = self.pipeline.decode_chunk(i, layer_mask=self.layer_mask)
75
+ self.queue.put(frame.cpu())
76
+ if not self._running:
77
+ break
78
+ else:
79
+ fps = self.pipeline.reader.header.fps
80
+ frame_time = 1.0 / fps if fps > 0 else 0.033
81
+ for i in range(self.pipeline.reader.num_chunks):
82
+ t0 = time.perf_counter()
83
+ frame = self.pipeline.decode_chunk(i, layer_mask=self.layer_mask)
84
+ self.queue.put(frame.cpu())
85
+ elapsed = time.perf_counter() - t0
86
+ sleep_time = frame_time - elapsed
87
+ if sleep_time > 0:
88
+ time.sleep(sleep_time)
89
+ if not self._running:
90
+ break
91
+ self.queue.close()
92
+
93
+ def play(self):
94
+ self.start()
95
+ try:
96
+ while True:
97
+ frame = self.queue.get()
98
+ self._display_frame(frame)
99
+ except (StopIteration, TimeoutError):
100
+ pass
101
+ finally:
102
+ self.stop()
103
+
104
+ def _display_frame(self, frame: torch.Tensor):
105
+ pass