dagloop5 commited on
Commit
d459f69
·
verified ·
1 Parent(s): ace7d16

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +37 -13
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
- chunk_paths = prior_paths + [path]
1083
- path = _concat_chunks(chunk_paths) if len(chunk_paths) > 1 else path
 
 
 
 
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 `(num_frames, 3,
1175
- H, W)` float in `[0, 1]` the format `MiniMaxH3MomentumConditionStep` expects for `given_video`. Strided
1176
- back down to 24 fps if the saved chunk was FILM-interpolated to a multiple of it: feeding the video VAE
1177
- frames at anything other than its own encoding rate would encode the motion at the wrong speed a
1178
- real correctness point, not a cosmetic one, since FILM's own multiplier is read fresh from the UI on every
1179
- press. Runs on CPU; no GPU time.
 
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
- start_frame = max(0, total_frames - round(seconds * actual_fps))
 
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 not frames:
1205
- return None
1206
- array = np.stack(frames).astype(np.float32) / 255.0
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