devkunalnaik commited on
Commit
7f39f97
·
1 Parent(s): a5190e9

Fix: XVID/AVI intermediate fixes zero-duration output, pix_fmt yuv420p for compatibility, FFmpeg error logging

Browse files
Files changed (1) hide show
  1. processors/video_processor.py +51 -21
processors/video_processor.py CHANGED
@@ -85,10 +85,16 @@ class VideoProcessor:
85
  if start_frame > 0:
86
  cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
87
 
88
- # Temp file for raw processed frames (mp4v codec)
89
- raw_out_path = tempfile.mktemp(suffix="_raw.mp4")
90
- fourcc = cv2.VideoWriter_fourcc(*"mp4v")
 
91
  writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (width, height))
 
 
 
 
 
92
 
93
  frame_idx = start_frame # absolute frame number in the source video
94
  processed = 0
@@ -205,34 +211,58 @@ class VideoProcessor:
205
  @staticmethod
206
  def _ffmpeg_encode(original_video_path: str, processed_raw_path: str, audio_start: float = 0.0) -> str:
207
  """
208
- Re-encode processed frames as H.264 and copy the original audio track.
209
- audio_start: seconds offset into the original audio (for resumed segments).
210
- Falls back to the raw file if FFmpeg is unavailable.
211
  """
212
  final_path = tempfile.mktemp(suffix="_output.mp4")
213
  try:
214
  import ffmpeg
 
215
 
216
- video_stream = ffmpeg.input(processed_raw_path).video
217
- # Seek audio to match the resumed start position
218
- audio_stream = ffmpeg.input(original_video_path, ss=audio_start).audio
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
220
  (
221
- ffmpeg.output(
222
- video_stream,
223
- audio_stream,
224
- final_path,
225
- vcodec="libx264",
226
- crf=23,
227
- preset="fast",
228
- acodec="aac",
229
- audio_bitrate="192k",
230
- )
231
  .overwrite_output()
232
- .run(quiet=True)
233
  )
 
 
 
 
 
234
  return final_path
235
 
 
 
 
 
 
236
  except Exception as e:
237
- print(f"[VideoProcessor] FFmpeg re-encode failed ({e}), returning raw file.")
238
  return processed_raw_path
 
85
  if start_frame > 0:
86
  cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
87
 
88
+ # Use AVI + XVID for the intermediate file far more reliable than
89
+ # mp4v on Linux (HF Spaces). FFmpeg converts it to H.264/mp4 after.
90
+ raw_out_path = tempfile.mktemp(suffix="_raw.avi")
91
+ fourcc = cv2.VideoWriter_fourcc(*"XVID")
92
  writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (width, height))
93
+ if not writer.isOpened():
94
+ # XVID not available — fall back to MJPG
95
+ raw_out_path = tempfile.mktemp(suffix="_raw.avi")
96
+ fourcc = cv2.VideoWriter_fourcc(*"MJPG")
97
+ writer = cv2.VideoWriter(raw_out_path, fourcc, fps, (width, height))
98
 
99
  frame_idx = start_frame # absolute frame number in the source video
100
  processed = 0
 
211
  @staticmethod
212
  def _ffmpeg_encode(original_video_path: str, processed_raw_path: str, audio_start: float = 0.0) -> str:
213
  """
214
+ Re-encode processed frames as H.264 mp4 and merge the original audio.
215
+ audio_start: seconds into the original audio (for resumed segments).
216
+ Returns the output path; raises if encoding fails so caller can report it.
217
  """
218
  final_path = tempfile.mktemp(suffix="_output.mp4")
219
  try:
220
  import ffmpeg
221
+ import subprocess
222
 
223
+ video_in = ffmpeg.input(processed_raw_path)
224
+ audio_in = ffmpeg.input(original_video_path)
225
+
226
+ # Build output streams
227
+ streams = [video_in.video]
228
+ # Only attach audio if the source has an audio track
229
+ try:
230
+ probe = ffmpeg.probe(original_video_path)
231
+ has_audio = any(s["codec_type"] == "audio" for s in probe["streams"])
232
+ except Exception:
233
+ has_audio = False
234
+
235
+ if has_audio:
236
+ if audio_start > 0:
237
+ audio_in = ffmpeg.input(original_video_path, ss=audio_start)
238
+ streams.append(audio_in.audio)
239
+
240
+ out_kwargs = dict(
241
+ vcodec="libx264",
242
+ crf=23,
243
+ preset="fast",
244
+ pix_fmt="yuv420p", # widest player compatibility
245
+ )
246
+ if has_audio:
247
+ out_kwargs.update(acodec="aac", audio_bitrate="192k")
248
 
249
  (
250
+ ffmpeg.output(*streams, final_path, **out_kwargs)
 
 
 
 
 
 
 
 
 
251
  .overwrite_output()
252
+ .run(quiet=False, capture_stdout=True, capture_stderr=True)
253
  )
254
+
255
+ # Validate output
256
+ if not os.path.exists(final_path) or os.path.getsize(final_path) < 1024:
257
+ raise RuntimeError("FFmpeg produced an empty output file.")
258
+
259
  return final_path
260
 
261
+ except ffmpeg.Error as e:
262
+ stderr = e.stderr.decode(errors="replace") if e.stderr else ""
263
+ print(f"[VideoProcessor] FFmpeg error:\n{stderr}")
264
+ # Return the raw file as fallback so the user gets something
265
+ return processed_raw_path
266
  except Exception as e:
267
+ print(f"[VideoProcessor] FFmpeg encode failed: {e}")
268
  return processed_raw_path