Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/Clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,11 @@ void Clip::SetJson(const std::string value) {

// Load Json::Value into this object
void Clip::SetJsonValue(const Json::Value root) {
auto ensure_default_keyframe = [](Keyframe& kf, double default_value) {
if (kf.GetCount() == 0) {
kf = Keyframe(default_value);
}
};

// Set parent data
ClipBase::SetJsonValue(root);
Expand Down Expand Up @@ -1110,6 +1115,16 @@ void Clip::SetJsonValue(const Json::Value root) {
perspective_c4_x.SetJsonValue(root["perspective_c4_x"]);
if (!root["perspective_c4_y"].isNull())
perspective_c4_y.SetJsonValue(root["perspective_c4_y"]);

// Core clip transforms should never remain empty after load. Empty JSON
// point arrays can be produced by editing flows that remove every keyframe.
ensure_default_keyframe(scale_x, 1.0);
ensure_default_keyframe(scale_y, 1.0);
ensure_default_keyframe(location_x, 0.0);
ensure_default_keyframe(location_y, 0.0);
ensure_default_keyframe(origin_x, 0.5);
ensure_default_keyframe(origin_y, 0.5);
ensure_default_keyframe(rotation, 0.0);
if (!root["effects"].isNull()) {

// Clear existing effects
Expand Down
68 changes: 68 additions & 0 deletions tests/Clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,74 @@ TEST_CASE( "Metadata rotation scales only default clips", "[libopenshot][clip]"
CHECK(custom_clip.scale_y.GetPoint(0).co.Y == Approx(0.5).margin(0.00001));
}

TEST_CASE( "SetJsonValue restores defaults for empty core transform keyframes", "[libopenshot][clip][json]" )
{
Clip clip;
clip.scale_x = Keyframe(2.0);
clip.scale_y = Keyframe(3.0);
clip.location_x = Keyframe(0.25);
clip.location_y = Keyframe(-0.5);
clip.origin_x = Keyframe(0.2);
clip.origin_y = Keyframe(0.8);
clip.rotation = Keyframe(45.0);

Json::Value root = clip.JsonValue();
root["scale_x"]["Points"] = Json::Value(Json::arrayValue);
root["scale_y"]["Points"] = Json::Value(Json::arrayValue);
root["location_x"]["Points"] = Json::Value(Json::arrayValue);
root["location_y"]["Points"] = Json::Value(Json::arrayValue);
root["origin_x"]["Points"] = Json::Value(Json::arrayValue);
root["origin_y"]["Points"] = Json::Value(Json::arrayValue);
root["rotation"]["Points"] = Json::Value(Json::arrayValue);

clip.SetJsonValue(root);

REQUIRE(clip.scale_x.GetCount() == 1);
REQUIRE(clip.scale_y.GetCount() == 1);
REQUIRE(clip.location_x.GetCount() == 1);
REQUIRE(clip.location_y.GetCount() == 1);
REQUIRE(clip.origin_x.GetCount() == 1);
REQUIRE(clip.origin_y.GetCount() == 1);
REQUIRE(clip.rotation.GetCount() == 1);

CHECK(clip.scale_x.GetValue(1) == Approx(1.0).margin(0.00001));
CHECK(clip.scale_y.GetValue(1) == Approx(1.0).margin(0.00001));
CHECK(clip.location_x.GetValue(1) == Approx(0.0).margin(0.00001));
CHECK(clip.location_y.GetValue(1) == Approx(0.0).margin(0.00001));
CHECK(clip.origin_x.GetValue(1) == Approx(0.5).margin(0.00001));
CHECK(clip.origin_y.GetValue(1) == Approx(0.5).margin(0.00001));
CHECK(clip.rotation.GetValue(1) == Approx(0.0).margin(0.00001));
}

TEST_CASE( "Timeline render remains visible after loading clip with empty core transform keyframes", "[libopenshot][clip][json][timeline]" )
{
std::stringstream path;
path << TEST_MEDIA_PATH << "front3.png";

Clip clip(path.str());
Json::Value root = clip.JsonValue();
root["scale_x"]["Points"] = Json::Value(Json::arrayValue);
root["scale_y"]["Points"] = Json::Value(Json::arrayValue);
root["location_x"]["Points"] = Json::Value(Json::arrayValue);
root["location_y"]["Points"] = Json::Value(Json::arrayValue);
root["rotation"]["Points"] = Json::Value(Json::arrayValue);
clip.SetJsonValue(root);

Timeline timeline(1280, 720, Fraction(30, 1), 44100, 2, LAYOUT_STEREO);
timeline.AddClip(&clip);
timeline.Open();

auto frame = timeline.GetFrame(1);
REQUIRE(frame != nullptr);
REQUIRE(frame->GetImage() != nullptr);

// Regression guard: the clip should still render into the timeline after
// loading empty transform keyframes from JSON.
CHECK(frame->GetImage()->pixelColor(200, 200).alpha() > 0);

timeline.Close();
}

TEST_CASE( "effects", "[libopenshot][clip]" )
{
// Load clip with video
Expand Down
Loading