Commit ·
c060ff8
1
Parent(s): c8b5875
Add TheBoldFont to static/fonts and use for all captions + add captions to story_reels
Browse files
modules/story_reels/services/story_creator.py
CHANGED
|
@@ -428,7 +428,8 @@ class StoryCreator:
|
|
| 428 |
await self._compose_video(
|
| 429 |
scenes=generated_scenes,
|
| 430 |
audio_path=mp3_path,
|
| 431 |
-
output_path=output_path
|
|
|
|
| 432 |
)
|
| 433 |
|
| 434 |
job["video_url"] = f"/videos/{job_id}.mp4"
|
|
@@ -457,7 +458,8 @@ class StoryCreator:
|
|
| 457 |
self,
|
| 458 |
scenes: List[Dict],
|
| 459 |
audio_path: Path,
|
| 460 |
-
output_path: Path
|
|
|
|
| 461 |
):
|
| 462 |
"""
|
| 463 |
Compose video from images and audio using MoviePy.
|
|
@@ -466,12 +468,14 @@ class StoryCreator:
|
|
| 466 |
- Crossfade transitions (0.3s) between scenes
|
| 467 |
- Subtle Ken Burns zoom (1.05x) for dynamic feel
|
| 468 |
- Fade in at start, fade out at end
|
|
|
|
| 469 |
"""
|
| 470 |
from moviepy.editor import (
|
| 471 |
ImageClip,
|
| 472 |
AudioFileClip,
|
| 473 |
concatenate_videoclips,
|
| 474 |
CompositeVideoClip,
|
|
|
|
| 475 |
vfx
|
| 476 |
)
|
| 477 |
|
|
@@ -557,6 +561,62 @@ class StoryCreator:
|
|
| 557 |
|
| 558 |
video = video.set_audio(audio)
|
| 559 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
# Write final video
|
| 561 |
logger.info(f"Writing video with effects: crossfade={CROSSFADE_DURATION}s, zoom={ZOOM_FACTOR}x")
|
| 562 |
video.write_videofile(
|
|
|
|
| 428 |
await self._compose_video(
|
| 429 |
scenes=generated_scenes,
|
| 430 |
audio_path=mp3_path,
|
| 431 |
+
output_path=output_path,
|
| 432 |
+
captions=captions_dict
|
| 433 |
)
|
| 434 |
|
| 435 |
job["video_url"] = f"/videos/{job_id}.mp4"
|
|
|
|
| 458 |
self,
|
| 459 |
scenes: List[Dict],
|
| 460 |
audio_path: Path,
|
| 461 |
+
output_path: Path,
|
| 462 |
+
captions: List[Dict] = None
|
| 463 |
):
|
| 464 |
"""
|
| 465 |
Compose video from images and audio using MoviePy.
|
|
|
|
| 468 |
- Crossfade transitions (0.3s) between scenes
|
| 469 |
- Subtle Ken Burns zoom (1.05x) for dynamic feel
|
| 470 |
- Fade in at start, fade out at end
|
| 471 |
+
- Word-by-word captions
|
| 472 |
"""
|
| 473 |
from moviepy.editor import (
|
| 474 |
ImageClip,
|
| 475 |
AudioFileClip,
|
| 476 |
concatenate_videoclips,
|
| 477 |
CompositeVideoClip,
|
| 478 |
+
TextClip,
|
| 479 |
vfx
|
| 480 |
)
|
| 481 |
|
|
|
|
| 561 |
|
| 562 |
video = video.set_audio(audio)
|
| 563 |
|
| 564 |
+
# ====================
|
| 565 |
+
# Add Captions Overlay
|
| 566 |
+
# ====================
|
| 567 |
+
if captions:
|
| 568 |
+
logger.info(f"Adding {len(captions)} captions to video...")
|
| 569 |
+
|
| 570 |
+
# Find font - Priority: TheBoldFont (static/fonts) > DejaVu > Liberation
|
| 571 |
+
thebold_path = Path(__file__).parent.parent.parent.parent / "static" / "fonts" / "THEBOLDFONT-FREEVERSION.ttf"
|
| 572 |
+
dejavu_path = Path("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
|
| 573 |
+
liberation_path = Path("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf")
|
| 574 |
+
|
| 575 |
+
if thebold_path.exists():
|
| 576 |
+
font_name = str(thebold_path)
|
| 577 |
+
elif dejavu_path.exists():
|
| 578 |
+
font_name = str(dejavu_path)
|
| 579 |
+
elif liberation_path.exists():
|
| 580 |
+
font_name = str(liberation_path)
|
| 581 |
+
else:
|
| 582 |
+
font_name = "DejaVu-Sans-Bold"
|
| 583 |
+
|
| 584 |
+
caption_clips = []
|
| 585 |
+
y_pos = TARGET_HEIGHT * 0.75 # 75% down (bottom area)
|
| 586 |
+
|
| 587 |
+
for cap in captions:
|
| 588 |
+
start_time = cap.get("startMs", 0) / 1000
|
| 589 |
+
end_time = cap.get("endMs", 0) / 1000
|
| 590 |
+
duration = end_time - start_time
|
| 591 |
+
text = cap.get("text", "")
|
| 592 |
+
|
| 593 |
+
if duration <= 0 or not text:
|
| 594 |
+
continue
|
| 595 |
+
|
| 596 |
+
try:
|
| 597 |
+
txt_clip = TextClip(
|
| 598 |
+
text,
|
| 599 |
+
fontsize=60,
|
| 600 |
+
color="white",
|
| 601 |
+
font=font_name,
|
| 602 |
+
stroke_color="black",
|
| 603 |
+
stroke_width=2,
|
| 604 |
+
method="caption",
|
| 605 |
+
size=(int(TARGET_WIDTH * 0.9), None),
|
| 606 |
+
align="center"
|
| 607 |
+
)
|
| 608 |
+
txt_clip = txt_clip.set_duration(duration)
|
| 609 |
+
txt_clip = txt_clip.set_start(start_time)
|
| 610 |
+
txt_clip = txt_clip.set_position(("center", y_pos))
|
| 611 |
+
caption_clips.append(txt_clip)
|
| 612 |
+
except Exception as e:
|
| 613 |
+
logger.warning(f"Caption failed: {e}")
|
| 614 |
+
continue
|
| 615 |
+
|
| 616 |
+
if caption_clips:
|
| 617 |
+
video = CompositeVideoClip([video] + caption_clips)
|
| 618 |
+
logger.info(f"Added {len(caption_clips)} caption clips")
|
| 619 |
+
|
| 620 |
# Write final video
|
| 621 |
logger.info(f"Writing video with effects: crossfade={CROSSFADE_DURATION}s, zoom={ZOOM_FACTOR}x")
|
| 622 |
video.write_videofile(
|
modules/video_creator/services/libraries/video_composer.py
CHANGED
|
@@ -285,19 +285,19 @@ class VideoComposer:
|
|
| 285 |
# Allocating 20% of height for caption box
|
| 286 |
|
| 287 |
# Try local font first, then system fonts
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
else:
|
| 292 |
-
|
| 293 |
-
dejavu_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
|
| 294 |
-
liberation_path = "/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf"
|
| 295 |
-
if Path(dejavu_path).exists():
|
| 296 |
-
font_name = dejavu_path
|
| 297 |
-
elif Path(liberation_path).exists():
|
| 298 |
-
font_name = liberation_path
|
| 299 |
-
else:
|
| 300 |
-
font_name = "DejaVu-Sans-Bold" # Fallback name
|
| 301 |
|
| 302 |
txt_clip = TextClip(
|
| 303 |
caption["text"],
|
|
|
|
| 285 |
# Allocating 20% of height for caption box
|
| 286 |
|
| 287 |
# Try local font first, then system fonts
|
| 288 |
+
# Priority: TheBoldFont (static/fonts) > DejaVu > Liberation
|
| 289 |
+
thebold_path = Path(__file__).parent.parent.parent.parent.parent / "static" / "fonts" / "THEBOLDFONT-FREEVERSION.ttf"
|
| 290 |
+
dejavu_path = Path("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf")
|
| 291 |
+
liberation_path = Path("/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf")
|
| 292 |
+
|
| 293 |
+
if thebold_path.exists():
|
| 294 |
+
font_name = str(thebold_path)
|
| 295 |
+
elif dejavu_path.exists():
|
| 296 |
+
font_name = str(dejavu_path)
|
| 297 |
+
elif liberation_path.exists():
|
| 298 |
+
font_name = str(liberation_path)
|
| 299 |
else:
|
| 300 |
+
font_name = "DejaVu-Sans-Bold" # Fallback name
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
txt_clip = TextClip(
|
| 303 |
caption["text"],
|
static/fonts/THEBOLDFONT-FREEVERSION.ttf
ADDED
|
Binary file (15.4 kB). View file
|
|
|