Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1079,8 +1079,12 @@ def generate(
|
|
| 1079 |
new_chunk_state = None
|
| 1080 |
if chunk_enabled:
|
| 1081 |
prior_paths = chunk_state["paths"] if chunk_advance else []
|
| 1082 |
-
|
| 1083 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
new_chunk_state = {"paths": chunk_paths}
|
| 1085 |
|
| 1086 |
return path, report, new_stage_state, new_chunk_state
|
|
@@ -1171,12 +1175,13 @@ def _last_frame_path(video_path: str) -> str | None:
|
|
| 1171 |
|
| 1172 |
|
| 1173 |
def _trailing_frames(video_path: str, seconds: float):
|
| 1174 |
-
"""The last `seconds` of `video_path`'s pixel frames, at MiniMax-H3's own native 24 fps, as
|
| 1175 |
-
H, W)`
|
| 1176 |
-
|
| 1177 |
-
|
| 1178 |
-
|
| 1179 |
-
|
|
|
|
| 1180 |
"""
|
| 1181 |
if not video_path or seconds <= 0:
|
| 1182 |
return None
|
|
@@ -1190,7 +1195,8 @@ def _trailing_frames(video_path: str, seconds: float):
|
|
| 1190 |
actual_fps = cap.get(cv2.CAP_PROP_FPS) or FPS
|
| 1191 |
stride = max(1, round(actual_fps / FPS))
|
| 1192 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 1193 |
-
|
|
|
|
| 1194 |
|
| 1195 |
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
| 1196 |
frames = []
|
|
@@ -1200,11 +1206,13 @@ def _trailing_frames(video_path: str, seconds: float):
|
|
| 1200 |
break
|
| 1201 |
if index % stride == 0:
|
| 1202 |
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
|
|
|
|
|
|
| 1203 |
cap.release()
|
| 1204 |
-
if
|
| 1205 |
-
return None
|
| 1206 |
-
array = np.stack(frames)
|
| 1207 |
-
return torch.from_numpy(array).permute(0, 3, 1, 2).contiguous()
|
| 1208 |
|
| 1209 |
|
| 1210 |
def _concat_chunks(paths: list[str]) -> str:
|
|
@@ -1226,6 +1234,22 @@ def _concat_chunks(paths: list[str]) -> str:
|
|
| 1226 |
)
|
| 1227 |
return out_path
|
| 1228 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1229 |
|
| 1230 |
load_models()
|
| 1231 |
|
|
|
|
| 1079 |
new_chunk_state = None
|
| 1080 |
if chunk_enabled:
|
| 1081 |
prior_paths = chunk_state["paths"] if chunk_advance else []
|
| 1082 |
+
# The momentum-imposed opening is a *regeneration* of the previous chunk's own tail, not new content —
|
| 1083 |
+
# trimmed here so concatenation doesn't duplicate it. Only continuation chunks that actually had momentum
|
| 1084 |
+
# applied carry anything to trim; chunk one, and any chunk that fell back to a plain keyframe carry, don't.
|
| 1085 |
+
chunk_output = _trim_head(path, float(momentum)) if (chunk_advance and given_video is not None) else path
|
| 1086 |
+
chunk_paths = prior_paths + [chunk_output]
|
| 1087 |
+
path = _concat_chunks(chunk_paths) if len(chunk_paths) > 1 else chunk_output
|
| 1088 |
new_chunk_state = {"paths": chunk_paths}
|
| 1089 |
|
| 1090 |
return path, report, new_stage_state, new_chunk_state
|
|
|
|
| 1175 |
|
| 1176 |
|
| 1177 |
def _trailing_frames(video_path: str, seconds: float):
|
| 1178 |
+
"""The last `snap_frames(seconds)` of `video_path`'s pixel frames, at MiniMax-H3's own native 24 fps, as
|
| 1179 |
+
`(num_frames, 3, H, W)` **uint8** — `encode_vae_condition`'s own documented input convention (it does its
|
| 1180 |
+
own `/255` and ImageNet normalization internally; pre-dividing here would double it). The frame count is
|
| 1181 |
+
snapped to the same `17 * n + 5` the video VAE's temporal chunking requires for a multi-frame encode, per
|
| 1182 |
+
that function's own docstring — the same alignment `snap_frames` already gives a full request. Strided back
|
| 1183 |
+
to 24 fps first if the saved chunk was FILM-interpolated to a multiple of it: encoding frames at the wrong
|
| 1184 |
+
rate would encode the motion at the wrong speed. Runs on CPU; no GPU time.
|
| 1185 |
"""
|
| 1186 |
if not video_path or seconds <= 0:
|
| 1187 |
return None
|
|
|
|
| 1195 |
actual_fps = cap.get(cv2.CAP_PROP_FPS) or FPS
|
| 1196 |
stride = max(1, round(actual_fps / FPS))
|
| 1197 |
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 1198 |
+
target_frames = snap_frames(seconds)
|
| 1199 |
+
start_frame = max(0, total_frames - target_frames * stride)
|
| 1200 |
|
| 1201 |
cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
|
| 1202 |
frames = []
|
|
|
|
| 1206 |
break
|
| 1207 |
if index % stride == 0:
|
| 1208 |
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 1209 |
+
if len(frames) == target_frames:
|
| 1210 |
+
break
|
| 1211 |
cap.release()
|
| 1212 |
+
if len(frames) < target_frames:
|
| 1213 |
+
return None # not enough source frames for a full, correctly-aligned encode
|
| 1214 |
+
array = np.stack(frames)
|
| 1215 |
+
return torch.from_numpy(array).permute(0, 3, 1, 2).contiguous() # uint8, (num_frames, 3, H, W)
|
| 1216 |
|
| 1217 |
|
| 1218 |
def _concat_chunks(paths: list[str]) -> str:
|
|
|
|
| 1234 |
)
|
| 1235 |
return out_path
|
| 1236 |
|
| 1237 |
+
def _trim_head(video_path: str, seconds: float) -> str:
|
| 1238 |
+
"""`video_path` with its first `seconds` cut off — the momentum-imposed opening a continuation chunk
|
| 1239 |
+
regenerates from the previous chunk's own tail, which would otherwise be duplicated once the chunks are
|
| 1240 |
+
concatenated. Re-encodes rather than stream-copying: an arbitrary, non-keyframe-aligned cut point can't
|
| 1241 |
+
always be trimmed losslessly with `-c copy`.
|
| 1242 |
+
"""
|
| 1243 |
+
import subprocess
|
| 1244 |
+
|
| 1245 |
+
directory = os.path.join(tempfile.gettempdir(), "pk-h3-chunks")
|
| 1246 |
+
os.makedirs(directory, exist_ok=True)
|
| 1247 |
+
out_path = os.path.join(directory, f"trimmed-{int(time.time() * 1000)}.mp4")
|
| 1248 |
+
subprocess.run(
|
| 1249 |
+
["ffmpeg", "-y", "-ss", str(seconds), "-i", video_path, out_path],
|
| 1250 |
+
check=True, capture_output=True,
|
| 1251 |
+
)
|
| 1252 |
+
return out_path
|
| 1253 |
|
| 1254 |
load_models()
|
| 1255 |
|