Slide transitions
Slide transitions
Configure how slides join in exported MP4 video. Default is none (hard cut).
Full reference deck: examples/slide-transitions-showcase.yaml — one edge per transition type.
Quick start
slide_transitions:
default: crossfade
duration_sec: 0.30
praisonaippt -i deck.yaml -o deck.pptx --convert-video
praisonaippt slide-transition-plan -i deck.yaml
Transition types
YAML type |
Visual effect | FFmpeg path | Re-encode |
|---|---|---|---|
none |
Hard cut | concat -c copy |
No |
segment_fade |
Dip to black in/out each clip | fade=t=in/out per segment |
Per segment only |
crossfade |
Dissolve A → B | xfade=transition=fade |
Yes (concat chain) |
wipeleft |
Wipe left | xfade=transition=wipeleft |
Yes |
wiperight |
Wipe right | xfade=transition=wiperight |
Yes |
slideleft |
Slide left | xfade=transition=slideleft |
Yes |
slideright |
Slide right | xfade=transition=slideright |
Yes |
!!! note “Deprecated alias”
YAML fade normalises to segment_fade with a deprecation warning. Legacy video_export.transition_fade_sec maps to { default: segment_fade, duration_sec: N }.
Architecture
flowchart LR
YAML[slide_transitions YAML]
VP[video_protocol.py]
ST[slide_transition.py]
VE[video_exporter.py]
FF[ffmpeg_composer.py]
TB[transition_backends.py]
YAML --> VP
VP --> ST
ST --> VE
VE --> FF
TB --> FF
| Module | Responsibility |
|---|---|
video_protocol.py |
Parse, resolve precedence, clamp duration, validate, effective_timeline_sec |
transition_backends.py |
Extensible backend registry → FFmpeg xfade names |
slide_transition.py |
maybe_apply_slide_transitions_deck, _slide_transitions sidecar |
video_exporter.py |
build_compose_plan, per-edge segment fade, SRT timing |
ffmpeg_composer.py |
build_xfade_filter_chain, concat_segments_with_transitions |
YAML schema
Top-level slide_transitions block
slide_transitions:
enabled: true # false → all edges resolve to none
default: none # global fallback
duration_sec: 0.30
min_slide_sec: 1.0 # skip transition when slide shorter
max_fade_ratio: 0.25 # cap duration vs slide length
edges:
- after_slide: 2
type: crossfade
duration_sec: 0.4
after_slide: N = transition leaving slide N → entering slide N+1 (1-based, matches slide plan order).
Nested under video_export
video_export:
transitions:
default: none
duration_sec: 0.30
video_crf: 23
When both are set, slide_transitions overrides video_export.transitions for defaults.
Per-verse
- reference: Slide A
text: "..."
transition_out: crossfade
transition_duration_sec: 0.35
Edge list as top-level list (alternate shape)
slide_transitions:
- after_slide: 1
type: segment_fade
duration_sec: 0.3
Resolution precedence
Later layers win:
slide_transitions.edges[]whereafter_slide == Nverse[N].transition_out(+ optionaltransition_duration_sec)slide_transitions.defaultorvideo_export.transitions.default- Legacy
video_export.transition_fade_sec > 0→segment_fade none
Short slides: clamp_transition_duration() may downgrade an edge to none when duration exceeds max_fade_ratio × slide_duration.
segment_fade vs crossfade
| segment_fade | crossfade / wipes | |
|---|---|---|
| When applied | Segment render (render_slide_segment) |
Concat (concat_segments_with_transitions) |
| Concat path | -c copy if no blend edges |
Full re-encode with xfade chain |
| Audio | Per-segment AAC | acrossfade on blend edges |
Mixed edges: when the outgoing edge is a blend, segment fade on that slide is skipped (avoids double-softening).
Showcase deck
examples/slide-transitions-showcase.yaml — eight verse slides, seven edges:
| After slide | Type | Notes |
|---|---|---|
| 1 | segment_fade |
Dip in/out on clip |
| 2 | crossfade |
Dissolve |
| 3 | wipeleft |
Overrides verse transition_out: crossfade |
| 4 | wiperight |
Directional wipe |
| 5 | slideleft |
Horizontal slide |
| 6 | slideright |
Horizontal slide |
| 7 | none |
Hard cut before final slide |
Built artefacts (repo):
| Artefact | Path |
|---|---|
| MP4 | examples/slide-transitions-showcase.mp4 |
| PPTX | examples/slide-transitions-showcase.pptx |
| Slide JPEGs | examples/slide_images/slide-transitions-showcase/slide-NNN.jpg |
| Captions | examples/slide-transitions-showcase.srt |
Transitions are visible in the MP4 only — JPEGs are static slide stills.
# Validate schema + transition gate
praisonaippt validate-deck -i examples/slide-transitions-showcase.yaml
# Print resolved matrix
praisonaippt slide-transition-plan -i examples/slide-transitions-showcase.yaml
# Preview one edge
praisonaippt slide-transition-preview -i examples/slide-transitions-showcase.yaml --slide 3
# Build PPTX + MP4 (requires ffmpeg, pdftoppm, libreoffice)
praisonaippt -i examples/slide-transitions-showcase.yaml \
-o examples/slide-transitions-showcase.pptx --convert-video
Expected plan output (edge column abbreviated):
After Type Dur(s) Source
1 segment_fade 0.300 edge
2 crossfade 0.350 edge
3 wipeleft 0.350 edge
4 wiperight 0.350 edge
5 slideleft 0.350 edge
6 slideright 0.350 edge
7 none 0.000 edge
Continuous avatar caveat
With avatar_timeline: continuous and blend transitions, lip-sync may drift. For HeyGen decks prefer per_slide or segment_fade only. A warning is logged at export time.
SRT captions
When any blend edge exists, write_srt uses effective_timeline_sec() (overlap subtracted). Hard-cut decks use the sum of duration_sec values.
CLI
| Command | Purpose |
|---|---|
slide-transition-plan -i deck.yaml [--force] |
Resolved edge matrix |
slide-transition-preview -i deck.yaml --slide N |
Transition leaving slide N |
Pipeline gate
| Gate | Step | When |
|---|---|---|
GATE_SLIDE_TRANSITIONS |
slide_transitions |
pipeline.validate_transitions: true (default) |
pipeline:
validate_transitions: true
strict_transitions: false # fail on warnings when true
Set slide_transitions.enabled: false to skip resolution entirely.
Sidecar after build hook: _slide_transitions on the deck dict (resolved edges + report).
Python API
from praisonaippt import (
load_verses_from_file,
maybe_apply_slide_transitions_deck,
resolve_edge_transitions,
TransitionDefaults,
format_transition_report,
list_transition_backends,
)
from praisonaippt.video_exporter import iter_slide_plan, build_compose_plan, VideoOptions
data = load_verses_from_file("examples/slide-transitions-showcase.yaml")
plan = list(iter_slide_plan(data))
data = maybe_apply_slide_transitions_deck(data, plan, source_file="deck.yaml")
print(data["_slide_transitions"]["report"])
# Low-level resolve
entries = [{"duration_sec": 4.0, "verse": v} for v in plan]
edges = resolve_edge_transitions(entries, data.get("video_export"), data.get("slide_transitions"))
print(format_transition_report(edges, slide_count=len(entries)))
print("Backends:", list_transition_backends())
Register a custom backend:
from praisonaippt.transition_backends import register_transition_backend
class MyBackend:
name = "my_fade"
requires_reencode = True
def ffmpeg_xfade_name(self):
return "fade"
register_transition_backend(MyBackend())
Validation checklist
Use this to confirm a local install matches the reference implementation:
| Check | Command / test |
|---|---|
| Default none (no fade filters) | Deck without transition keys → fade_sec=0 in segments |
| Protocol resolve precedence | pytest tests/test_video_transitions.py |
| YAML schema | pytest tests/test_yaml_validate.py -k slide_transitions |
| FFmpeg filter chain | pytest tests/test_ffmpeg_transitions.py |
| Backend registry | pytest tests/test_transition_backends.py |
| Pipeline gate | pytest tests/test_pipeline_ci_gates.py -k slide_transitions |
| Showcase deck loads | praisonaippt validate-deck -i examples/slide-transitions-showcase.yaml |
| Plan matrix | praisonaippt slide-transition-plan -i examples/slide-transitions-showcase.yaml |
Related
- Video export — compositor, narration, presets
- Deck reference (YAML) —
slide_transitions,video_export.transitions - Commands — CLI reference
- Pipeline architecture — gates and hooks
- Slide QA —
GATE_SLIDE_TRANSITIONSinreport.json - Examples — showcase build commands