ismdrobiul489 commited on
Commit
37dbf21
·
1 Parent(s): 4697873

Fix video playback: strict 2s duration, chain method (no overlap), audio duration cap, and ffmpeg browser compatibility (yuv420p)

Browse files
modules/story_reels/services/story_creator.py CHANGED
@@ -438,12 +438,14 @@ class StoryCreator:
438
  if result.get("path"):
439
  temp_files.append(Path(result["path"]))
440
 
441
- # Find matching chunk for duration
442
  scene_duration = 2.0
443
- for chunk in image_chunks:
444
- if chunk['chunk_id'] == result["id"]:
445
- scene_duration = chunk['duration']
446
- break
 
 
447
 
448
  generated_scenes.append({
449
  "scene_id": result["id"],
@@ -597,28 +599,34 @@ class StoryCreator:
597
  # Set duration
598
  clip = clip.set_duration(duration)
599
 
600
- # Simple Zoom Effect (Ken Burns)
601
- # We apply a slight zoom (1.0 -> 1.05) to all clips for movement
602
- if duration > 0:
603
- clip = clip.resize(lambda t: 1 + 0.04 * t / duration)
604
- # Re-crop to ensure no borders appear during zoom
605
- clip = clip.crop(x_center=clip.w/2, y_center=clip.h/2, width=TARGET_WIDTH, height=TARGET_HEIGHT)
606
 
607
  # Transitions
608
  if i > 0:
609
- clip = clip.crossfadein(CROSSFADE_DURATION)
610
 
611
  clips.append(clip)
 
612
  total_video_duration += duration
613
 
614
- # Concatenate clips
615
- # method="compose" is safer for crossfades
616
- video = concatenate_videoclips(clips, method="compose", padding=-CROSSFADE_DURATION)
 
 
 
617
 
618
- # Final safety: match video length to audio exactly
619
- if abs(video.duration - audio_duration) > 0.1:
620
- if video.duration > audio_duration:
621
- video = video.subclip(0, audio_duration)
 
 
 
 
622
 
623
  video = video.set_audio(audio)
624
 
 
438
  if result.get("path"):
439
  temp_files.append(Path(result["path"]))
440
 
441
+ # Force 2.0 seconds per image as requested
442
  scene_duration = 2.0
443
+
444
+ # (Optional) We can still look up chunk for debug/metadata but duration is fixed
445
+ # for chunk in image_chunks:
446
+ # if chunk['chunk_id'] == result["id"]:
447
+ # # scene_duration = chunk['duration'] # Ignored
448
+ # break
449
 
450
  generated_scenes.append({
451
  "scene_id": result["id"],
 
599
  # Set duration
600
  clip = clip.set_duration(duration)
601
 
602
+ # Removed dynamic zoom to fix black screen/rendering issues
603
+ # Images will be static but stable
604
+
605
+ # Transitions
 
 
606
 
607
  # Transitions
608
  if i > 0:
609
+ clip = clip.crossfadein(0.5) # Simple fade-in for smooth join without overlap
610
 
611
  clips.append(clip)
612
+ # For strict chain method, total is sum of durations
613
  total_video_duration += duration
614
 
615
+ # Concatenate clips (Chain Method: one after another)
616
+ # padding=0 ensures no overlap, preserving exact duration calculation
617
+ if len(clips) > 1:
618
+ video = concatenate_videoclips(clips, method="compose")
619
+ else:
620
+ video = clips[0]
621
 
622
+ # Final safety: FORCE video duration to match audio exactly
623
+ # If video is longer, cut it. If shorter, it's fine (but our logic tries to match)
624
+ if video.duration > audio_duration:
625
+ logger.info(f"Trimming video from {video.duration}s to match audio {audio_duration}s")
626
+ video = video.subclip(0, audio_duration)
627
+ else:
628
+ # Ideally shouldn't happen with our chain logic, but if slightly short, set duration
629
+ video = video.set_duration(audio_duration)
630
 
631
  video = video.set_audio(audio)
632