Skip to content

Commit 286e737

Browse files
committed
fix: extract video segments for HTML subsections with accurate timing
HTML now extracts subsection video segments like PowerPoint does: - Extract segments from start_time to end_time with accurate=True - Each subsection gets its own video file (re-encoded for frame accuracy) - Tail sections for content after last subsection - Only triggered when subsections are present This fixes timing issues in HTML where browser video seeking to start/end times was not frame-accurate (keyframe-based). Now HTML uses pre-extracted video segments with exact frame-perfect cuts.
1 parent f6b592a commit 286e737

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

manim_slides/convert.py

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -588,31 +588,35 @@ def _iter_slide_sections(self, slide_config: SlideConfig) -> list[dict[str, Any]
588588

589589
sections = []
590590
last_end = 0.0
591-
for subsection in slide_config.subsections:
591+
for index, subsection in enumerate(slide_config.subsections):
592+
fragment_file = Path(
593+
f"{slide_config.file.stem}_sub_{index}{slide_config.file.suffix}"
594+
)
592595
sections.append(
593596
{
594-
"file": slide_config.file,
597+
"file": fragment_file,
595598
"loop": False,
596599
"auto_next": subsection.auto_next,
597600
"notes": f"{slide_config.notes}\n\n{subsection.name}"
598601
if slide_config.notes and subsection.name
599602
else subsection.name or slide_config.notes,
600-
"start_time": subsection.start_time,
601-
"end_time": subsection.end_time,
603+
"start_time": None,
604+
"end_time": None,
602605
}
603606
)
604607
last_end = subsection.end_time
605608

606609
video_duration = get_duration_seconds(slide_config.file)
607610
if video_duration - last_end > 1e-3:
611+
tail_file = Path(f"{slide_config.file.stem}_tail{slide_config.file.suffix}")
608612
sections.append(
609613
{
610-
"file": slide_config.file,
614+
"file": tail_file,
611615
"loop": slide_config.loop,
612616
"auto_next": False,
613617
"notes": slide_config.notes,
614-
"start_time": last_end,
615-
"end_time": video_duration,
618+
"start_time": None,
619+
"end_time": None,
616620
}
617621
)
618622
return sections
@@ -654,10 +658,47 @@ def prefix(i: int) -> str:
654658
return ""
655659

656660
full_assets_dir.mkdir(parents=True, exist_ok=True)
657-
for i, presentation_config in enumerate(self.presentation_configs):
658-
presentation_config.copy_to(
659-
full_assets_dir, include_reversed=False, prefix=prefix(i)
660-
)
661+
662+
if self.subsection_mode == SubsectionMode.all:
663+
for i, presentation_config in enumerate(self.presentation_configs):
664+
for slide_config in presentation_config.slides:
665+
if slide_config.subsections:
666+
for index, subsection in enumerate(slide_config.subsections):
667+
fragment_file = full_assets_dir / (
668+
prefix(i)
669+
+ f"{slide_config.file.stem}_sub_{index}{slide_config.file.suffix}"
670+
)
671+
extract_video_segment(
672+
slide_config.file,
673+
fragment_file,
674+
subsection.start_time,
675+
subsection.end_time,
676+
accurate=True,
677+
)
678+
679+
video_duration = get_duration_seconds(slide_config.file)
680+
last_end = slide_config.subsections[-1].end_time
681+
if video_duration - last_end > 1e-3:
682+
fragment_file = full_assets_dir / (
683+
prefix(i)
684+
+ f"{slide_config.file.stem}_tail{slide_config.file.suffix}"
685+
)
686+
extract_video_segment(
687+
slide_config.file,
688+
fragment_file,
689+
last_end,
690+
video_duration,
691+
accurate=True,
692+
)
693+
else:
694+
dest_file = full_assets_dir / (prefix(i) + slide_config.file.name)
695+
if not dest_file.exists():
696+
shutil.copy(slide_config.file, dest_file)
697+
else:
698+
for i, presentation_config in enumerate(self.presentation_configs):
699+
presentation_config.copy_to(
700+
full_assets_dir, include_reversed=False, prefix=prefix(i)
701+
)
661702

662703
dest.parent.mkdir(parents=True, exist_ok=True)
663704

manim_slides/templates/revealjs.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
{% for section in enriched_slide.sections %}
2727
{%- set section_loop = loop %}
2828
{% if one_file %}
29-
{% set file = file_to_data_uri(slide_config.file) %}
29+
{% set file = file_to_data_uri(section.file) %}
3030
{% else %}
31-
{% set file = assets_dir / (prefix(outer_loop.index0) + slide_config.file.name) %}
31+
{% set file = assets_dir / (prefix(outer_loop.index0) + section.file.name) %}
3232
{% endif %}
3333
<section
3434
data-background-size={{ background_size }}

0 commit comments

Comments
 (0)