devendergarg14 commited on
Commit
2edbb24
·
verified ·
1 Parent(s): f99f3d5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -46
app.py CHANGED
@@ -146,7 +146,11 @@ def add_speed_message(msg, speed_factor):
146
 
147
 
148
  def make_video(s, video_in, audio_in, trim_frames=0, speed_factor=1.0):
149
- """Compile frames before the source video, then apply speed to the complete result."""
 
 
 
 
150
  v_path = p(video_in)
151
  audio_path = p(audio_in)
152
  if not v_path:
@@ -163,42 +167,49 @@ def make_video(s, video_in, audio_in, trim_frames=0, speed_factor=1.0):
163
  if trim_frames > 0 and not has_frames:
164
  return None, "Extract frames first to use initial-frame trimming."
165
 
166
- # With no extracted frames, keep the original video (and its original audio)
167
- # unless the user supplies replacement audio.
 
 
 
 
 
 
 
 
168
  if not has_frames:
169
- _, _, _, duration = meta(v_path)
170
- if not audio_path:
171
- out = v_path
172
- msg = "✅ Final video created from the original video. No extracted-frame intro was added."
173
- else:
174
- out = os.path.join(tempfile.mkdtemp(), f"final_audio_{int(time.time() * 1000)}.mp4")
 
 
175
  replacement_duration = audio_meta(audio_path)
176
- loop_args = ["-stream_loop", "-1"] if replacement_duration > 0 and (duration - replacement_duration) > 1.0 else []
177
  cmd = ["ffmpeg", "-y", "-i", v_path] + loop_args + [
178
  "-i", audio_path,
179
  "-map", "0:v:0", "-map", "1:a:0",
180
- "-t", str(duration),
181
  "-c:v", "copy", "-c:a", "aac", "-b:a", "192k",
182
- "-movflags", "+faststart", out,
183
  ]
184
  result = subprocess.run(cmd, capture_output=True, text=True)
185
  if result.returncode:
186
  return None, result.stderr or "Could not replace the audio."
187
  msg = "✅ Final video created using the original video and replacement audio."
188
- out = apply_speed_to_video(out, speed_factor)
189
- return out, add_speed_message(msg, speed_factor)
190
-
191
- # Use the video associated with the extracted frames and its original frame rate.
192
- frame_state_video = s["v"]
193
- frame_dir, frames, fps, source_duration = s["d"], s["fs"], s["fps"], s["dur"]
194
- trim_seconds = trim_frames / fps
195
- if trim_seconds >= source_duration:
196
- return None, f"Cannot remove {trim_frames} frame(s): the video is only {source_duration:.3f}s long."
197
 
 
 
 
 
198
  intro = os.path.join(frame_dir, "intro.mp4")
199
  r = subprocess.run(
200
  [
201
- "ffmpeg", "-y", "-framerate", str(fps),
202
  "-i", os.path.join(frame_dir, "f_%06d.png"),
203
  "-c:v", "libx264", "-crf", "18", "-preset", "medium",
204
  "-pix_fmt", "yuv420p", intro,
@@ -208,36 +219,57 @@ def make_video(s, video_in, audio_in, trim_frames=0, speed_factor=1.0):
208
  if r.returncode:
209
  return None, r.stderr or "Could not compile extracted frames."
210
 
211
- intro_duration = len(frames) / fps
212
- total_duration = intro_duration + (source_duration - trim_seconds)
 
213
  out = os.path.join(tempfile.mkdtemp(), f"final_{int(time.time() * 1000)}.mp4")
214
- video_concat = (
215
- f"[1:v:0]trim=start_frame={trim_frames},setpts=PTS-STARTPTS[bodyv];"
216
- "[0:v:0][bodyv]concat=n=2:v=1:a=0[vout]"
 
 
 
 
 
 
 
 
217
  )
218
 
 
219
  if audio_path:
220
  replacement_duration = audio_meta(audio_path)
221
  loop_args = ["-stream_loop", "-1"] if replacement_duration > 0 and (total_duration - replacement_duration) > 1.0 else []
222
- cmd = ["ffmpeg", "-y", "-i", intro, "-i", frame_state_video] + loop_args + [
223
- "-i", audio_path,
224
- "-filter_complex", video_concat,
225
  "-map", "[vout]", "-map", "2:a:0",
226
  "-t", str(total_duration),
227
  "-c:v", "libx264", "-crf", "18", "-preset", "medium",
228
  "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k",
229
- "-movflags", "+faststart", out,
230
- ]
231
  else:
232
- cmd = [
233
- "ffmpeg", "-y", "-i", intro, "-i", frame_state_video,
234
- "-filter_complex", video_concat,
235
- "-map", "[vout]", "-map", "1:a?",
236
- "-t", str(total_duration),
237
- "-c:v", "libx264", "-crf", "18", "-preset", "medium",
238
- "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k",
239
- "-movflags", "+faststart", out,
240
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
242
  result = subprocess.run(cmd, capture_output=True, text=True)
243
  if result.returncode:
@@ -245,16 +277,22 @@ def make_video(s, video_in, audio_in, trim_frames=0, speed_factor=1.0):
245
 
246
  msg = f"✅ Final video created. Added {len(frames)} extracted frame(s)."
247
  if trim_frames:
248
- msg += f" Removed the first {trim_frames} source frame(s) ({trim_seconds:.3f}s)."
 
 
249
  if audio_path:
250
  if loop_args:
251
  msg += f" Replacement audio looped (it was {total_duration - replacement_duration:.1f}s short)."
252
  else:
253
  msg += " Replacement audio added."
 
 
254
  else:
255
- msg += " Original video audio retained when available."
256
- out = apply_speed_to_video(out, speed_factor)
257
- return out, add_speed_message(msg, speed_factor)
 
 
258
 
259
  except Exception as e:
260
  return None, str(e)
@@ -275,7 +313,7 @@ with gr.Blocks(title="Video Frame Editor") as demo:
275
  minimum=0,
276
  precision=0,
277
  label="Initial Frames to Remove from Original Video",
278
- info="Set to 0 to keep the original beginning. Applies when extracted frames are added first.",
279
  )
280
 
281
  with gr.Row():
 
146
 
147
 
148
  def make_video(s, video_in, audio_in, trim_frames=0, speed_factor=1.0):
149
+ """Compile the extracted-frame intro with the currently selected, independently trimmed source video.
150
+
151
+ Extracted frames keep their existing extraction timing. Trimming affects only the source-video
152
+ section, then the whole compiled output is sped up/slowed down as the final operation.
153
+ """
154
  v_path = p(video_in)
155
  audio_path = p(audio_in)
156
  if not v_path:
 
167
  if trim_frames > 0 and not has_frames:
168
  return None, "Extract frames first to use initial-frame trimming."
169
 
170
+ # Always use the currently selected input as the original-video segment. The extracted
171
+ # frame state is used only to build the intro, so those two parts stay independent.
172
+ _, _, source_fps, source_duration = meta(v_path)
173
+ trim_seconds = trim_frames / source_fps
174
+ if trim_frames and trim_seconds >= source_duration:
175
+ return None, (
176
+ f"Cannot remove {trim_frames} source frame(s): the selected video is only "
177
+ f"{source_duration:.3f}s long at {source_fps:.3f} FPS."
178
+ )
179
+
180
  if not has_frames:
181
+ # With no intro frames, trim the source video if requested (the UI normally expects
182
+ # a frame intro for trimming, but this branch remains safe if called directly).
183
+ if trim_frames:
184
+ return None, "Extract frames first to use initial-frame trimming."
185
+ base_out = v_path
186
+ msg = "✅ Final video created from the original video. No extracted-frame intro was added."
187
+ if audio_path:
188
+ base_out = os.path.join(tempfile.mkdtemp(), f"final_audio_{int(time.time() * 1000)}.mp4")
189
  replacement_duration = audio_meta(audio_path)
190
+ loop_args = ["-stream_loop", "-1"] if replacement_duration > 0 and (source_duration - replacement_duration) > 1.0 else []
191
  cmd = ["ffmpeg", "-y", "-i", v_path] + loop_args + [
192
  "-i", audio_path,
193
  "-map", "0:v:0", "-map", "1:a:0",
194
+ "-t", str(source_duration),
195
  "-c:v", "copy", "-c:a", "aac", "-b:a", "192k",
196
+ "-movflags", "+faststart", base_out,
197
  ]
198
  result = subprocess.run(cmd, capture_output=True, text=True)
199
  if result.returncode:
200
  return None, result.stderr or "Could not replace the audio."
201
  msg = "✅ Final video created using the original video and replacement audio."
202
+ final_out = apply_speed_to_video(base_out, speed_factor)
203
+ return final_out, add_speed_message(msg, speed_factor)
 
 
 
 
 
 
 
204
 
205
+ # Build the intro from the extracted frames with the same frame rate as extraction,
206
+ # preserving the existing behavior. Trimming below is calculated from the current source.
207
+ frame_dir, frames = s["d"], s["fs"]
208
+ intro_fps = float(s["fps"])
209
  intro = os.path.join(frame_dir, "intro.mp4")
210
  r = subprocess.run(
211
  [
212
+ "ffmpeg", "-y", "-framerate", str(intro_fps),
213
  "-i", os.path.join(frame_dir, "f_%06d.png"),
214
  "-c:v", "libx264", "-crf", "18", "-preset", "medium",
215
  "-pix_fmt", "yuv420p", intro,
 
219
  if r.returncode:
220
  return None, r.stderr or "Could not compile extracted frames."
221
 
222
+ intro_duration = len(frames) / intro_fps
223
+ body_duration = source_duration - trim_seconds
224
+ total_duration = intro_duration + body_duration
225
  out = os.path.join(tempfile.mkdtemp(), f"final_{int(time.time() * 1000)}.mp4")
226
+
227
+ # Normalize the source segment after trimming, then concatenate it after the intro.
228
+ # Scaling/padding the intro to source dimensions avoids concat failures if the selected
229
+ # source video differs from the video used to extract the frames.
230
+ width, height, _, _ = meta(v_path)
231
+ video_filter = (
232
+ f"[0:v:0]scale={width}:{height}:force_original_aspect_ratio=decrease,"
233
+ f"pad={width}:{height}:(ow-iw)/2:(oh-ih)/2,setsar=1,fps={source_fps:.8f}[intro];"
234
+ f"[1:v:0]trim=start_frame={trim_frames},setpts=PTS-STARTPTS,"
235
+ f"fps={source_fps:.8f},setsar=1[body];"
236
+ "[intro][body]concat=n=2:v=1:a=0[vout]"
237
  )
238
 
239
+ cmd = ["ffmpeg", "-y", "-i", intro, "-i", v_path]
240
  if audio_path:
241
  replacement_duration = audio_meta(audio_path)
242
  loop_args = ["-stream_loop", "-1"] if replacement_duration > 0 and (total_duration - replacement_duration) > 1.0 else []
243
+ cmd += loop_args + ["-i", audio_path,
244
+ "-filter_complex", video_filter,
 
245
  "-map", "[vout]", "-map", "2:a:0",
246
  "-t", str(total_duration),
247
  "-c:v", "libx264", "-crf", "18", "-preset", "medium",
248
  "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k",
249
+ "-movflags", "+faststart", out]
 
250
  else:
251
+ if stream_has_audio(v_path):
252
+ # Keep source audio aligned with the trimmed video section. The intro is silent.
253
+ audio_filter = (
254
+ f"{video_filter};[1:a:0]atrim=start={trim_seconds:.8f},"
255
+ f"asetpts=PTS-STARTPTS,adelay={int(round(intro_duration * 1000))}:all=1[aout]"
256
+ )
257
+ cmd += [
258
+ "-filter_complex", audio_filter,
259
+ "-map", "[vout]", "-map", "[aout]",
260
+ "-t", str(total_duration),
261
+ "-c:v", "libx264", "-crf", "18", "-preset", "medium",
262
+ "-pix_fmt", "yuv420p", "-c:a", "aac", "-b:a", "192k",
263
+ "-movflags", "+faststart", out,
264
+ ]
265
+ else:
266
+ cmd += [
267
+ "-filter_complex", video_filter,
268
+ "-map", "[vout]", "-an",
269
+ "-t", str(total_duration),
270
+ "-c:v", "libx264", "-crf", "18", "-preset", "medium",
271
+ "-pix_fmt", "yuv420p", "-movflags", "+faststart", out,
272
+ ]
273
 
274
  result = subprocess.run(cmd, capture_output=True, text=True)
275
  if result.returncode:
 
277
 
278
  msg = f"✅ Final video created. Added {len(frames)} extracted frame(s)."
279
  if trim_frames:
280
+ msg += f" Original video starts after skipping {trim_frames} frame(s) ({trim_seconds:.3f}s at {source_fps:.3f} FPS)."
281
+ else:
282
+ msg += " Original video starts from its beginning."
283
  if audio_path:
284
  if loop_args:
285
  msg += f" Replacement audio looped (it was {total_duration - replacement_duration:.1f}s short)."
286
  else:
287
  msg += " Replacement audio added."
288
+ elif stream_has_audio(v_path):
289
+ msg += " Original audio is trimmed to match the original-video section; the intro is silent."
290
  else:
291
+ msg += " The source video has no audio track."
292
+
293
+ # Speed is deliberately the final step, so it affects the intro and original video together.
294
+ final_out = apply_speed_to_video(out, speed_factor)
295
+ return final_out, add_speed_message(msg, speed_factor)
296
 
297
  except Exception as e:
298
  return None, str(e)
 
313
  minimum=0,
314
  precision=0,
315
  label="Initial Frames to Remove from Original Video",
316
+ info="Affects only the original-video section, not the extracted-frame intro. Frame count is based on the currently selected original video FPS.",
317
  )
318
 
319
  with gr.Row():