113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
"""Tests for fcpxml/emphasis.py — the emphasis index (pure, no audio needed)."""
|
|
|
|
from fcpxml.emphasis import (
|
|
EmphasisWeights,
|
|
annotate_emphasis,
|
|
compute_emphasis,
|
|
pause_weight,
|
|
)
|
|
|
|
|
|
def test_zero_input_gives_zero_score():
|
|
assert compute_emphasis(0.0, 0.0, 0.0, 0.0, 0.0) == 0.0
|
|
|
|
|
|
def test_max_input_gives_max_score():
|
|
# pause sits inside the "dramatic beat" window, not a scene-change gap
|
|
score = compute_emphasis(
|
|
energy=1.0, pitch_delta=1.0, rate_delta=1.0, pause_before=1.5, word_duration=10.0
|
|
)
|
|
assert score == 1.0
|
|
|
|
|
|
def test_negative_pitch_delta_uses_magnitude():
|
|
a = compute_emphasis(0.0, pitch_delta=0.5, rate_delta=0.0, pause_before=0.0, word_duration=0.0)
|
|
b = compute_emphasis(0.0, pitch_delta=-0.5, rate_delta=0.0, pause_before=0.0, word_duration=0.0)
|
|
assert a == b > 0.0
|
|
|
|
|
|
def test_duration_saturates_beyond_cap():
|
|
at_cap = compute_emphasis(0.0, 0.0, 0.0, 0.0, word_duration=1.0, max_duration=1.0)
|
|
beyond = compute_emphasis(0.0, 0.0, 0.0, 0.0, word_duration=30.0, max_duration=1.0)
|
|
assert at_cap == beyond
|
|
|
|
|
|
class TestPauseWeight:
|
|
"""A long gap is a scene change, not emphasis — it must not outrank a
|
|
word the speaker actually hit hard. Grounded in real footage where 6-9s
|
|
gaps were topping the emphasis ranking."""
|
|
|
|
def test_dramatic_beat_counts_fully(self):
|
|
assert pause_weight(1.5, max_pause=1.5) == 1.0
|
|
|
|
def test_short_beat_counts_proportionally(self):
|
|
assert pause_weight(0.75, max_pause=1.5) == 0.5
|
|
|
|
def test_long_gap_is_ignored(self):
|
|
assert pause_weight(8.7, max_pause=1.5, ignore_above=3.0) == 0.0
|
|
|
|
def test_no_pause_is_zero(self):
|
|
assert pause_weight(0.0) == 0.0
|
|
|
|
def test_cutoff_can_be_disabled(self):
|
|
assert pause_weight(30.0, max_pause=1.5, ignore_above=0.0) == 1.0
|
|
|
|
def test_scene_change_scores_below_a_loud_word(self):
|
|
gap = compute_emphasis(0.25, 0.04, 0.0, pause_before=6.2, word_duration=0.5)
|
|
loud = compute_emphasis(1.00, 0.28, 0.0, pause_before=1.9, word_duration=0.2)
|
|
assert loud > gap
|
|
|
|
|
|
def test_higher_energy_weight_increases_energy_contribution():
|
|
low_weight = EmphasisWeights(energy=0.1, pitch_variation=0.0, rate_variation=0.0, pause_before=0.0, duration=0.0)
|
|
high_weight = EmphasisWeights(energy=1.0, pitch_variation=0.0, rate_variation=0.0, pause_before=0.0, duration=0.0)
|
|
# energy is the only nonzero factor for both weight sets, so normalized
|
|
# score should be identical regardless of the absolute weight value.
|
|
score_low = compute_emphasis(0.6, 0.0, 0.0, 0.0, 0.0, weights=low_weight)
|
|
score_high = compute_emphasis(0.6, 0.0, 0.0, 0.0, 0.0, weights=high_weight)
|
|
assert score_low == score_high
|
|
|
|
|
|
def test_all_zero_weights_returns_zero_not_error():
|
|
zero_weights = EmphasisWeights(0.0, 0.0, 0.0, 0.0, 0.0)
|
|
assert compute_emphasis(1.0, 1.0, 1.0, 1.0, 1.0, weights=zero_weights) == 0.0
|
|
|
|
|
|
def test_weights_round_trip_dict():
|
|
w = EmphasisWeights(energy=0.4, pitch_variation=0.3, rate_variation=0.1, pause_before=0.1, duration=0.1)
|
|
restored = EmphasisWeights.from_dict(w.as_dict())
|
|
assert restored == w
|
|
|
|
|
|
def test_from_dict_fills_missing_with_defaults():
|
|
restored = EmphasisWeights.from_dict({"energy": 0.9})
|
|
defaults = EmphasisWeights()
|
|
assert restored.energy == 0.9
|
|
assert restored.pitch_variation == defaults.pitch_variation
|
|
|
|
|
|
def test_annotate_emphasis_adds_score_per_word():
|
|
words = [
|
|
{"word": "hi", "start": 0.0, "end": 0.3, "energy": 0.2, "pitch_delta": 0.1, "rate_delta": 0.1, "pause_before": 0.0},
|
|
{"word": "WOW", "start": 1.0, "end": 1.5, "energy": 0.9, "pitch_delta": 0.8, "rate_delta": 0.7, "pause_before": 1.0},
|
|
]
|
|
annotated = annotate_emphasis(words)
|
|
assert len(annotated) == 2
|
|
assert all("emphasis" in w for w in annotated)
|
|
assert annotated[1]["emphasis"] > annotated[0]["emphasis"]
|
|
|
|
|
|
def test_annotate_emphasis_does_not_mutate_input():
|
|
words = [{"word": "hi", "start": 0.0, "end": 0.3, "energy": 0.5}]
|
|
annotate_emphasis(words)
|
|
assert "emphasis" not in words[0]
|
|
|
|
|
|
def test_annotate_emphasis_derives_duration_from_start_end():
|
|
"""A word with no explicit "duration" key gets it from end - start."""
|
|
base = {"energy": 0.0, "pitch_delta": 0.0, "rate_delta": 0.0, "pause_before": 0.0}
|
|
derived = annotate_emphasis([{"word": "hi", "start": 1.0, "end": 1.5, **base}], max_duration=0.5)
|
|
explicit = annotate_emphasis([{"word": "hi", "start": 0.0, "end": 0.0, "duration": 0.5, **base}], max_duration=0.5)
|
|
# duration is the only nonzero factor in both, so scores must match
|
|
assert derived[0]["emphasis"] == explicit[0]["emphasis"] > 0.0
|